[DOC-CVS] [phd] master: Add support for constants in attribute formatting (#255)

[email protected] (Jordi Kroon via GitHub) Mon, 22 Jun 2026 16:40:29 +0000
Newsgroups php.doc.cvs
Message-ID <[email protected]>
Author: Jordi Kroon (jordikroon)
Committer: GitHub (web-flow)
Pusher: jordikroon
Date: 2026-06-22T18:40:25+02:00

Commit: https://github.com/php/phd/commit/88859c86dcbc74d7339a0c10d1bc903fcfeeb12f
Raw diff: https://github.com/php/phd/commit/88859c86dcbc74d7339a0c10d1bc903fcfeeb12f.diff

Add support for constants in attribute formatting (#255)

* add support for constants in attribute formatting

Changed paths:
  A  tests/package/generic/attribute_formatting_004.phpt
  A  tests/package/generic/data/attribute_formatting_004.xml
  M  phpdotnet/phd/Package/Generic/XHTML.php
  M  tests/package/generic/attribute_formatting_001.phpt
  M  tests/package/generic/attribute_formatting_002.phpt
  M  tests/package/generic/attribute_formatting_003.phpt


Diff:

diff --git a/phpdotnet/phd/Package/Generic/XHTML.php b/phpdotnet/phd/Package/Generic/XHTML.php
index b5ab2b90..1491fb1a 100644
--- a/phpdotnet/phd/Package/Generic/XHTML.php
+++ b/phpdotnet/phd/Package/Generic/XHTML.php
@@ -1449,12 +1449,7 @@ public function format_fieldsynopsis($open, $name, $attrs) {
     public function format_fieldsynopsis_modifier_text($value, $tag) {
         $this->cchunk["fieldsynopsis"]["modifier"] = trim($value);
         if ($this->getRole() === "attribute") {
-            $attribute = trim(strtolower($value), "#[]\\");
-            $href = Format::getFilename("class.$attribute");
-            if ($href) {
-                return '<a href="' . $href . $this->getExt() . '">' .$value. '</a> ';
-            }
-            return false;
+            return $this->format_attribute_modifier_text($value);
         }
         return $this->TEXT($value);
     }
@@ -1492,15 +1487,31 @@ public function format_methodparam_modifier($open, $name, $attrs, $props) {
 
     public function format_modifier_text($value, $tag) {
         if ($this->getRole() === "attribute") {
-            $attribute = trim(strtolower($value), "#[]\\");
-            $href = Format::getFilename("class.$attribute");
-            if ($href) {
-                return '<a href="' . $href . $this->getExt() . '">' .$value. '</a> ';
-            }
+            return $this->format_attribute_modifier_text($value);
         }
         return false;
     }
 
+    private function format_attribute_modifier_text(string $value): string {
+        // Anything that is not a leading "#[\Attribute(" / "#[\Attribute]" chunk
+        // e.g. "|" separator between arguments passes through.
+        if (!preg_match('/^(#\[)(.+?)([](])$/', $value, $match)) {
+            if (trim($value) === '|') {
+                return ' | ';
+            }
+            return $value;
+        }
+
+        [, $prefix, $name, $suffix] = $match;
+        $attribute = strtolower(ltrim($name, "\\"));
+        $href = $this->getFilename('class.' . $attribute);
+        if (!$href) {
+            return $value;
+        }
+
+        return $prefix . '<a href="' . $href . $this->getExt() . '">' . $name . '</a>' . $suffix;
+    }
+
     public function format_methodsynopsis($open, $name, $attrs, $props) {
         if ($open) {
 
@@ -1909,6 +1920,9 @@ public function format_screen($open, $name, $attrs) {
     }
     public function format_constant($open, $name, $attrs, $props)
     {
+        if ($this->getRole() === "attribute") {
+            return "";
+        }
         if ($open) {
             if (str_contains($props["innerXml"], '<replaceable')) {
                 $this->pushRole("constant_group");
diff --git a/tests/package/generic/attribute_formatting_001.phpt b/tests/package/generic/attribute_formatting_001.phpt
index 76bbf787..5c4c6d2b 100644
--- a/tests/package/generic/attribute_formatting_001.phpt
+++ b/tests/package/generic/attribute_formatting_001.phpt
@@ -42,7 +42,7 @@ Content:
 
  <div class="section">
   <p class="para">2. Class methodparameter with known attribute</p>
-  <div class="constructorsynopsis dc-description"><span class="modifier">public</span>  <span class="methodname">mysqli::__construct</span>(<span class="methodparam"><span class="attribute"><a href="file.knownattribute.is.in.html">#[\KnownAttribute]</a> </span><span class="type"><span class="type">string</span><span class="type">null</span></span> <code class="parameter">$password</code><span class="initializer"> = <span class="type">null</span></span></span>)</div>
+  <div class="constructorsynopsis dc-description"><span class="modifier">public</span>  <span class="methodname">mysqli::__construct</span>(<span class="methodparam"><span class="attribute">#[<a href="file.knownattribute.is.in.html">\KnownAttribute</a>]</span><span class="type"><span class="type">string</span><span class="type">null</span></span> <code class="parameter">$password</code><span class="initializer"> = <span class="type">null</span></span></span>)</div>
 
  </div>
 
@@ -54,7 +54,7 @@ Content:
 
  <div class="section">
   <p class="para">4. Function parameter with known attribute</p>
-  <div class="methodsynopsis dc-description"><span class="type">bool</span> <span class="methodname">password_verify</span>(<span class="methodparam"><span class="attribute"><a href="file.knownattribute.is.in.html">#[\KnownAttribute]</a> </span><span class="type">string</span> <code class="parameter">$password</code></span>, <span class="methodparam"><span class="type">string</span> <code class="parameter">$hash</code></span>)</div>
+  <div class="methodsynopsis dc-description"><span class="type">bool</span> <span class="methodname">password_verify</span>(<span class="methodparam"><span class="attribute">#[<a href="file.knownattribute.is.in.html">\KnownAttribute</a>]</span><span class="type">string</span> <code class="parameter">$password</code></span>, <span class="methodparam"><span class="type">string</span> <code class="parameter">$hash</code></span>)</div>
 
  </div>
 </div>
diff --git a/tests/package/generic/attribute_formatting_002.phpt b/tests/package/generic/attribute_formatting_002.phpt
index 6e7aec9d..b5e0d17f 100644
--- a/tests/package/generic/attribute_formatting_002.phpt
+++ b/tests/package/generic/attribute_formatting_002.phpt
@@ -62,8 +62,8 @@ Content:
   <p class="para">2. Class with known attributes</p>
   <div class="classsynopsis"><div class="classsynopsisinfo">
    
-    <span class="attribute"><a href="file.knownattribute.is.in.html">#[\KnownAttribute]</a> </span><br>
-    <span class="attribute"><a href="file.anotherknownattribute.is.in.html">#[\AnotherKnownAttribute]</a> </span><br>
+    <span class="attribute">#[<a href="file.knownattribute.is.in.html">\KnownAttribute</a>]</span><br>
+    <span class="attribute">#[<a href="file.anotherknownattribute.is.in.html">\AnotherKnownAttribute</a>]</span><br>
     <span class="modifier">class</span> <strong class="classname">DateTime</strong>
     {</div>
   }</div>
@@ -80,8 +80,8 @@ Content:
 
  <div class="section">
   <p class="para">4. Method with known attributes</p>
-  <div class="methodsynopsis dc-description"><span class="attribute"><a href="file.knownattribute.is.in.html">#[\KnownAttribute]</a> </span><br>
-   <span class="attribute"><a href="file.anotherknownattribute.is.in.html">#[\AnotherKnownAttribute]</a> </span><br>
+  <div class="methodsynopsis dc-description"><span class="attribute">#[<a href="file.knownattribute.is.in.html">\KnownAttribute</a>]</span><br>
+   <span class="attribute">#[<a href="file.anotherknownattribute.is.in.html">\AnotherKnownAttribute</a>]</span><br>
    <span class="modifier">public</span>  <span class="methodname">ClassName::methodName</span>()</div>
 
  </div>
@@ -96,8 +96,8 @@ Content:
 
  <div class="section">
   <p class="para">6. Constructor with known attributes</p>
-  <div class="constructorsynopsis dc-description"><span class="attribute"><a href="file.knownattribute.is.in.html">#[\KnownAttribute]</a> </span><br>
-   <span class="attribute"><a href="file.anotherknownattribute.is.in.html">#[\AnotherKnownAttribute]</a> </span><br>
+  <div class="constructorsynopsis dc-description"><span class="attribute">#[<a href="file.knownattribute.is.in.html">\KnownAttribute</a>]</span><br>
+   <span class="attribute">#[<a href="file.anotherknownattribute.is.in.html">\AnotherKnownAttribute</a>]</span><br>
    <span class="modifier">public</span>  <span class="methodname">ClassName::__construct</span>()</div>
 
  </div>
@@ -130,21 +130,21 @@ Content:
   <p class="para">8. Class, constructor and methods with known attributes</p>
   <div class="classsynopsis"><div class="classsynopsisinfo">
    
-    <span class="attribute"><a href="file.knownattribute.is.in.html">#[\KnownAttribute]</a> </span><br>
-    <span class="attribute"><a href="file.anotherknownattribute.is.in.html">#[\AnotherKnownAttribute]</a> </span><br>
+    <span class="attribute">#[<a href="file.knownattribute.is.in.html">\KnownAttribute</a>]</span><br>
+    <span class="attribute">#[<a href="file.anotherknownattribute.is.in.html">\AnotherKnownAttribute</a>]</span><br>
     <span class="modifier">class</span> <strong class="classname">DateTime</strong>
     {</div>
    <div class="constructorsynopsis dc-description">
-    <span class="attribute"><a href="file.knownattribute.is.in.html">#[\KnownAttribute]</a> </span><br>
-    <span class="attribute"><a href="file.anotherknownattribute.is.in.html">#[\AnotherKnownAttribute]</a> </span><br>
+    <span class="attribute">#[<a href="file.knownattribute.is.in.html">\KnownAttribute</a>]</span><br>
+    <span class="attribute">#[<a href="file.anotherknownattribute.is.in.html">\AnotherKnownAttribute</a>]</span><br>
     <span class="modifier">public</span>  <span class="methodname">ClassName::__construct</span>()</div>
 
-   <div class="methodsynopsis dc-description"><span class="attribute"><a href="file.knownattribute.is.in.html">#[\KnownAttribute]</a> </span><br>
-    <span class="attribute"><a href="file.anotherknownattribute.is.in.html">#[\AnotherKnownAttribute]</a> </span><br>
+   <div class="methodsynopsis dc-description"><span class="attribute">#[<a href="file.knownattribute.is.in.html">\KnownAttribute</a>]</span><br>
+    <span class="attribute">#[<a href="file.anotherknownattribute.is.in.html">\AnotherKnownAttribute</a>]</span><br>
     <span class="modifier">public</span>  <span class="methodname">ClassName::methodName1</span>()</div>
 
-   <div class="methodsynopsis dc-description"><span class="attribute"><a href="file.knownattribute.is.in.html">#[\KnownAttribute]</a> </span><br>
-    <span class="attribute"><a href="file.anotherknownattribute.is.in.html">#[\AnotherKnownAttribute]</a> </span><br>
+   <div class="methodsynopsis dc-description"><span class="attribute">#[<a href="file.knownattribute.is.in.html">\KnownAttribute</a>]</span><br>
+    <span class="attribute">#[<a href="file.anotherknownattribute.is.in.html">\AnotherKnownAttribute</a>]</span><br>
     <span class="modifier">public</span>  <span class="methodname">ClassName::methodName2</span>()</div>
 
   }</div>
@@ -161,8 +161,8 @@ Content:
 
  <div class="section">
   <p class="para">10. Function with known attributes</p>
-  <div class="methodsynopsis dc-description"><span class="attribute"><a href="file.knownattribute.is.in.html">#[\KnownAttribute]</a> </span><br>
-   <span class="attribute"><a href="file.anotherknownattribute.is.in.html">#[\AnotherKnownAttribute]</a> </span><br>
+  <div class="methodsynopsis dc-description"><span class="attribute">#[<a href="file.knownattribute.is.in.html">\KnownAttribute</a>]</span><br>
+   <span class="attribute">#[<a href="file.anotherknownattribute.is.in.html">\AnotherKnownAttribute</a>]</span><br>
    <span class="type">void</span> <span class="methodname">function_name</span>)</div>
 
  </div>
diff --git a/tests/package/generic/attribute_formatting_003.phpt b/tests/package/generic/attribute_formatting_003.phpt
index 00ce7e77..0506abaa 100644
--- a/tests/package/generic/attribute_formatting_003.phpt
+++ b/tests/package/generic/attribute_formatting_003.phpt
@@ -75,8 +75,8 @@ Content:
     {</div>
    <div class="classsynopsisinfo classsynopsisinfo_comment">/* Properties/Constants */</div>
    <div class="fieldsynopsis">
-    <span class="attribute"><a href="file.knownattribute.is.in.html">#[\KnownAttribute]</a> </span><br>
-    <span class="attribute"><a href="file.anotherknownattribute.is.in.html">#[\AnotherKnownAttribute]</a> </span><br>
+    <span class="attribute">#[<a href="file.knownattribute.is.in.html">\KnownAttribute</a>]</span><br>
+    <span class="attribute">#[<a href="file.anotherknownattribute.is.in.html">\AnotherKnownAttribute</a>]</span><br>
     <span class="modifier">public</span>
     <span class="modifier">readonly</span>
     <span class="type">string</span>
diff --git a/tests/package/generic/attribute_formatting_004.phpt b/tests/package/generic/attribute_formatting_004.phpt
new file mode 100644
index 00000000..fca533ac
--- /dev/null
+++ b/tests/package/generic/attribute_formatting_004.phpt
@@ -0,0 +1,91 @@
+--TEST--
+Attribute formatting 004 - Attribute with constant arguments
+--FILE--
+<?php
+namespace phpdotnet\phd;
+
+require_once __DIR__ . "/../../setup.php";
+
+$xmlFile = __DIR__ . "/data/attribute_formatting_004.xml";
+
+$config->xmlFile = $xmlFile;
+
+$format = new TestGenericChunkedXHTML($config, $outputHandler);
+
+$format->SQLiteIndex(
+    null, null,
+    "class.attribute",
+    "class.attribute",
+    "", "", "", "", "", "", 0,
+);
+$format->SQLiteIndex(
+    null, null,
+    "attribute.constants.target-class",
+    "class.attribute",
+    "", "", "", "", "", "", 0,
+);
+$format->SQLiteIndex(
+    null, null,
+    "attribute.constants.target-class-constant",
+    "class.attribute",
+    "", "", "", "", "", "", 0,
+);
+$format->SQLiteIndex(
+    null, null,
+    "attribute.constants.target-property",
+    "class.attribute",
+    "", "", "", "", "", "", 0,
+);
+
+$render = new TestRender(new Reader($outputHandler), $config, $format);
+
+$render->run();
+?>
+--EXPECT--
+Filename: attribute-formatting-004.html
+Content:
+<div id="attribute-formatting-004" class="chapter">
+ <div class="section">
+  <p class="para">1. Attribute with one constant argument</p>
+  <div class="classsynopsis"><div class="classsynopsisinfo">
+   
+    <span class="attribute">#[<a href="class.attribute.html">\Attribute</a>(<a href="class.attribute.html#attribute.constants.target-class">Attribute::TARGET_CLASS</a>)]</span><br>
+    <span class="modifier">final</span>
+    <span class="modifier">class</span> <strong class="classname">Attribute</strong>
+    {</div>
+  }</div>
+ </div>
+
+ <div class="section">
+  <p class="para">2. Attribute with multiple constant arguments</p>
+  <div class="classsynopsis"><div class="classsynopsisinfo">
+   
+    <span class="attribute">#[<a href="class.attribute.html">\Attribute</a>(<a href="class.attribute.html#attribute.constants.target-class-constant">Attribute::TARGET_CLASS_CONSTANT</a> | <a href="class.attribute.html#attribute.constants.target-property">Attribute::TARGET_PROPERTY</a>)]</span><br>
+    <span class="modifier">final</span>
+    <span class="modifier">class</span> <strong class="classname">Attribute</strong>
+    {</div>
+  }</div>
+ </div>
+
+ <div class="section">
+  <p class="para">3. Attribute with unknown constant argument</p>
+  <div class="classsynopsis"><div class="classsynopsisinfo">
+   
+    <span class="attribute">#[<a href="class.attribute.html">\Attribute</a>(Attribute::TARGET_UNKNOWN)]</span><br>
+    <span class="modifier">final</span>
+    <span class="modifier">class</span> <strong class="classname">Attribute</strong>
+    {</div>
+  }</div>
+ </div>
+
+ <div class="section">
+  <p class="para">4. Unknown attribute with constant argument</p>
+  <div class="classsynopsis"><div class="classsynopsisinfo">
+   
+    <span class="attribute">#[\UnknownAttribute(<a href="class.attribute.html#attribute.constants.target-class">Attribute::TARGET_CLASS</a>)]</span><br>
+    <span class="modifier">final</span>
+    <span class="modifier">class</span> <strong class="classname">Attribute</strong>
+    {</div>
+  }</div>
+ </div>
+</div>
diff --git a/tests/package/generic/data/attribute_formatting_004.xml b/tests/package/generic/data/attribute_formatting_004.xml
new file mode 100644
index 00000000..ef67532d
--- /dev/null
+++ b/tests/package/generic/data/attribute_formatting_004.xml
@@ -0,0 +1,45 @@
+<chapter xml:id="attribute-formatting-004">
+ <section>
+  <para>1. Attribute with one constant argument</para>
+  <classsynopsis class="class">
+   <ooclass>
+    <modifier role="attribute">#[\Attribute(<constant>Attribute::TARGET_CLASS</constant>)]</modifier>
+    <modifier>final</modifier>
+    <classname>Attribute</classname>
+   </ooclass>
+  </classsynopsis>
+ </section>
+
+ <section>
+  <para>2. Attribute with multiple constant arguments</para>
+  <classsynopsis class="class">
+   <ooclass>
+    <modifier role="attribute">#[\Attribute(<constant>Attribute::TARGET_CLASS_CONSTANT</constant>|<constant>Attribute::TARGET_PROPERTY</constant>)]</modifier>
+    <modifier>final</modifier>
+    <classname>Attribute</classname>
+   </ooclass>
+  </classsynopsis>
+ </section>
+
+ <section>
+  <para>3. Attribute with unknown constant argument</para>
+  <classsynopsis class="class">
+   <ooclass>
+    <modifier role="attribute">#[\Attribute(<constant>Attribute::TARGET_UNKNOWN</constant>)]</modifier>
+    <modifier>final</modifier>
+    <classname>Attribute</classname>
+   </ooclass>
+  </classsynopsis>
+ </section>
+
+ <section>
+  <para>4. Unknown attribute with constant argument</para>
+  <classsynopsis class="class">
+   <ooclass>
+    <modifier role="attribute">#[\UnknownAttribute(<constant>Attribute::TARGET_CLASS</constant>)]</modifier>
+    <modifier>final</modifier>
+    <classname>Attribute</classname>
+   </ooclass>
+  </classsynopsis>
+ </section>
+</chapter>