[phd] attribute-constants-v2: improve attribute formatting with better argument parsing
| From: | Jordi Kroon | Date: | Fri, 11 Sep 2026 14:26:06 +0000 |
| Subject: | [phd] attribute-constants-v2: improve attribute formatting with better argument parsing | ||
| Groups: | php.doc.cvs | ||
| Request: | Send a blank email to doc-cvs+get-23745@lists.php.net to get a copy of this message | ||
Author: Jordi Kroon (jordikroon)
Date: 2026-09-11T16:04:41+02:00
Commit: https://github.com/php/phd/commit/4f117227b7dc35719ce3e853e37899e3070fc4fe
Raw diff: https://github.com/php/phd/commit/4f117227b7dc35719ce3e853e37899e3070fc4fe.diff
improve attribute formatting with better argument parsing
Changed paths:
A tests/package/generic/attribute_formatting_005.phpt
A tests/package/generic/data/attribute_formatting_005.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 6719d01de..c9bea71fb 100644
--- a/phpdotnet/phd/Package/Generic/XHTML.php
+++ b/phpdotnet/phd/Package/Generic/XHTML.php
@@ -1491,23 +1491,150 @@ public function format_modifier_text($value, $tag) {
}
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 ' | ';
+ $trimmed = trim($value);
+ $namePattern = '\\\\?[A-Za-z_][A-Za-z0-9_\\\\]*';
+
+ // Full attribute with literal arguments: #[\Name(args)]
+ if (preg_match('/^(#\[)(' . $namePattern . ')\((.+)\)]$/s', $trimmed,
$match)) {
+ [, $prefix, $name, $args] = $match;
+ return $prefix . $this->link_attribute_name($name) . '(' .
$this->render_attribute_args($args) . ')]';
+ }
+
+ // Simple attribute: #[\Name]
+ if (preg_match('/^#\[(' . $namePattern . ')]$/', $trimmed, $match)) {
+ $name = $match[1];
+ $attribute = strtolower(ltrim($name, "\\"));
+ $href = $this->getFilename('class.' . $attribute);
+ $token = '#[' . $name . ']';
+ if (!$href) {
+ return $token;
}
- return $value;
+
+ return '<a href="' . $href . $this->getExt() .
'">' . $token . '</a> ';
}
- [, $prefix, $name, $suffix] = $match;
+ // Opening of an attribute followed by child elements: #[\Name(
+ if (preg_match('/^(#\[)(' . $namePattern . ')\($/', $trimmed, $match))
{
+ [, $prefix, $name] = $match;
+ return $prefix . $this->link_attribute_name($name) . '(';
+ }
+
+ // Separator between attribute arguments
+ if ($trimmed === '|') {
+ return ' | ';
+ }
+
+ return $trimmed === '' ? '' : $value;
+ }
+
+ private function link_attribute_name(string $name): string {
$attribute = strtolower(ltrim($name, "\\"));
$href = $this->getFilename('class.' . $attribute);
if (!$href) {
- return $value;
+ return $name;
+ }
+
+ return '<a href="' . $href . $this->getExt() . '">'
. $name . '</a>';
+ }
+
+ private function render_attribute_args(string $args): string {
+ $args = trim(preg_replace('/\s+/', ' ', $args));
+ $parts = $this->split_attribute_args($args);
+
+ if (count($parts) <= 1) {
+ return $this->link_constants_in_text($parts[0]['value'] ?? '');
+ }
+
+ $lines = [];
+ $prefix = '';
+ foreach ($parts as $part) {
+ $rendered = $this->link_constants_in_text($part['value']);
+ $line = ' ' . $prefix . $rendered;
+ if ($part['separator'] === ',') {
+ $line .= ',';
+ $prefix = '';
+ } elseif ($part['separator'] === '|') {
+ $prefix = '| ';
+ } else {
+ $prefix = '';
+ }
+ $lines[] = $line;
+ }
+
+ return '<br>' . implode('<br>', $lines) .
'<br>';
+ }
+
+ /**
+ * @return list<array{value: string, separator: string|null}>
+ */
+ private function split_attribute_args(string $args): array {
+ $parts = [];
+ $current = '';
+ $depth = 0;
+ $inString = false;
+ $stringChar = '';
+ $length = strlen($args);
+
+ for ($i = 0; $i < $length; $i++) {
+ $ch = $args[$i];
+
+ if ($inString) {
+ $current .= $ch;
+ if ($ch === '\\' && $i + 1 < $length) {
+ $current .= $args[++$i];
+ continue;
+ }
+ if ($ch === $stringChar) {
+ $inString = false;
+ }
+ continue;
+ }
+
+ if ($ch === '"' || $ch === "'") {
+ $inString = true;
+ $stringChar = $ch;
+ $current .= $ch;
+ continue;
+ }
+
+ if ($ch === '(' || $ch === '[') {
+ $depth++;
+ } elseif ($ch === ')' || $ch === ']') {
+ $depth--;
+ }
+
+ if ($depth === 0 && ($ch === '|' || $ch === ',')) {
+ $parts[] = ['value' => trim($current), 'separator' =>
$ch];
+ $current = '';
+ continue;
+ }
+
+ $current .= $ch;
+ }
+
+ if (trim($current) !== '') {
+ $parts[] = ['value' => trim($current), 'separator' => null];
}
- return $prefix . '<a href="' . $href . $this->getExt() .
'">' . $name . '</a>' . $suffix;
+ return $parts;
+ }
+
+ private function link_constants_in_text(string $text): string {
+ $escaped = htmlspecialchars($text, ENT_NOQUOTES, 'UTF-8');
+
+ // Link Class::CONST references that appear as literal text
+ return preg_replace_callback(
+ '/\\\\?[A-Za-z_][\w\\\\]*::[A-Za-z_][\w]*/',
+ function (array $match): string {
+ $constant = $match[0];
+ $link = $this->createLink($this->convertConstantNameToId($constant));
+ if ($link === null) {
+ return $constant;
+ }
+ return '<a href="' . $link . '">' . $constant .
'</a>';
+ },
+ $escaped,
+ );
}
public function format_methodsynopsis($open, $name, $attrs, $props) {
diff --git a/tests/package/generic/attribute_formatting_001.phpt
b/tests/package/generic/attribute_formatting_001.phpt
index 5c4c6d2b6..76bbf787f 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 b5e0d17f7..ef6f021f0 100644
--- a/tests/package/generic/attribute_formatting_002.phpt
+++ b/tests/package/generic/attribute_formatting_002.phpt
@@ -50,7 +50,7 @@ Content:
<div class="section">
<p class="para">1. Class with unknown attributes</p>
<div class="classsynopsis"><div class="classsynopsisinfo">
-
+
<span class="attribute">#[\UnknownAttribute]</span><br>
<span class="attribute">#[\AnotherUnknownAttribute]</span><br>
<span class="modifier">class</span> <strong
class="classname">DateTime</strong>
@@ -61,9 +61,9 @@ Content:
<div class="section">
<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>
@@ -105,7 +105,7 @@ Content:
<div class="section">
<p class="para">7. Class, constructor and methods with unknown
attributes</p>
<div class="classsynopsis"><div class="classsynopsisinfo">
-
+
<span class="attribute">#[\UnknownAttribute]</span><br>
<span class="attribute">#[\AnotherUnknownAttribute]</span><br>
<span class="modifier">class</span> <strong
class="classname">DateTime</strong>
@@ -129,22 +129,22 @@ Content:
<div class="section">
<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 0506abaa3..b1066f9cd 100644
--- a/tests/package/generic/attribute_formatting_003.phpt
+++ b/tests/package/generic/attribute_formatting_003.phpt
@@ -52,7 +52,7 @@ Content:
<div class="section">
<p class="para">1. Property/Constant with unknown attributes</p>
<div class="classsynopsis"><div class="classsynopsisinfo">
-
+
<span class="modifier">class</span> <strong
class="classname">ClassName</strong>
{</div>
<div class="classsynopsisinfo classsynopsisinfo_comment">/* Properties/Constants
*/</div>
@@ -70,13 +70,13 @@ Content:
<div class="section">
<p class="para">2. Property/Constant with known attributes</p>
<div class="classsynopsis"><div class="classsynopsisinfo">
-
+
<span class="modifier">class</span> <strong
class="classname">ClassName</strong>
{</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>
@@ -88,7 +88,7 @@ Content:
<div class="section">
<p class="para">3. Constant of a class with attribute</p>
<div class="classsynopsis"><div class="classsynopsisinfo">
-
+
<span class="attribute">#[\UnknownAttribute]</span><br>
<span class="modifier">class</span> <strong
class="classname">ClassName</strong>
{</div>
diff --git a/tests/package/generic/attribute_formatting_005.phpt
b/tests/package/generic/attribute_formatting_005.phpt
new file mode 100644
index 000000000..96e95aa1f
--- /dev/null
+++ b/tests/package/generic/attribute_formatting_005.phpt
@@ -0,0 +1,119 @@
+--TEST--
+Attribute formatting 005 - Attribute with literal parameter arguments
+--FILE--
+<?php
+namespace phpdotnet\phd;
+
+require_once __DIR__ . "/../../setup.php";
+
+$xmlFile = __DIR__ . "/data/attribute_formatting_005.xml";
+
+$config->xmlFile = $xmlFile;
+
+$format = new TestGenericChunkedXHTML($config, $outputHandler);
+
+$format->SQLiteIndex(
+ null, null,
+ "class.deprecated",
+ "class.deprecated",
+ "", "", "", "", "", "", 0,
+);
+$format->SQLiteIndex(
+ null, null,
+ "class.attribute",
+ "class.attribute",
+ "", "", "", "", "", "", 0,
+);
+$format->SQLiteIndex(
+ null, null,
+ "attribute.constants.target-function",
+ "class.attribute",
+ "", "", "", "", "", "", 0,
+);
+$format->SQLiteIndex(
+ null, null,
+ "attribute.constants.target-method",
+ "class.attribute",
+ "", "", "", "", "", "", 0,
+);
+$format->SQLiteIndex(
+ null, null,
+ "attribute.constants.target-class",
+ "class.attribute",
+ "", "", "", "", "", "", 0,
+);
+
+$render = new TestRender(new Reader($outputHandler), $config, $format);
+
+$render->run();
+?>
+--EXPECT--
+Filename: attribute-formatting-005.html
+Content:
+<div id="attribute-formatting-005" class="chapter">
+ <div class="section">
+ <p class="para">1. Attribute with literal named arguments</p>
+ <div class="classsynopsis"><div class="classsynopsisinfo">
+
+ <span class="attribute">#[<a
href="class.deprecated.html">\Deprecated</a>(<br> since:
'8.5',<br> message: 'Deprecated since
PHP 8.4'<br>)]</span><br>
+ <span class="modifier">final</span>
+ <span class="modifier">class</span> <strong
class="classname">Deprecated</strong>
+ {</div>
+ }</div>
+ </div>
+
+ <div class="section">
+ <p class="para">2. Attribute with single literal positional argument</p>
+ <div class="classsynopsis"><div class="classsynopsisinfo">
+
+ <span class="attribute">#[<a
href="class.deprecated.html">\Deprecated</a>('Deprecated since PHP
8.4')]</span><br>
+ <span class="modifier">final</span>
+ <span class="modifier">class</span> <strong
class="classname">Deprecated</strong>
+ {</div>
+ }</div>
+ </div>
+
+ <div class="section">
+ <p class="para">3. Unknown attribute with literal argument</p>
+ <div class="classsynopsis"><div class="classsynopsisinfo">
+
+ <span class="attribute">#[\UnknownAttribute(foo:
'bar')]</span><br>
+ <span class="modifier">final</span>
+ <span class="modifier">class</span> <strong
class="classname">Deprecated</strong>
+ {</div>
+ }</div>
+ </div>
+
+ <div class="section">
+ <p class="para">4. Namespaced attribute with literal argument</p>
+ <div class="classsynopsis"><div class="classsynopsisinfo">
+
+ <span class="attribute">#[\Some\Namespaced\Attribute(value:
42)]</span><br>
+ <span class="modifier">final</span>
+ <span class="modifier">class</span> <strong
class="classname">Deprecated</strong>
+ {</div>
+ }</div>
+ </div>
+
+ <div class="section">
+ <p class="para">5. Multi-line attribute with literal class constant
arguments</p>
+ <div class="classsynopsis"><div class="classsynopsisinfo">
+
+ <span class="attribute">#[<a
href="class.attribute.html">\Attribute</a>(<br> <a
href="class.attribute.html#attribute.constants.target-function">Attribute::TARGET_FUNCTION</a><br> |
<a
href="class.attribute.html#attribute.constants.target-method">Attribute::TARGET_METHOD</a><br> |
<a
href="class.attribute.html#attribute.constants.target-class">Attribute::TARGET_CLASS</a><br>)]</span><br>
+ <span class="modifier">final</span>
+ <span class="modifier">class</span> <strong
class="classname">Deprecated</strong>
+ {</div>
+ }</div>
+ </div>
+
+ <div class="section">
+ <p class="para">6. Attribute with mix of known and unknown class
constants</p>
+ <div class="classsynopsis"><div class="classsynopsisinfo">
+
+ <span class="attribute">#[<a
href="class.attribute.html">\Attribute</a>(<br> <a
href="class.attribute.html#attribute.constants.target-class">Attribute::TARGET_CLASS</a><br> |
Unknown::CONST<br>)]</span><br>
+ <span class="modifier">final</span>
+ <span class="modifier">class</span> <strong
class="classname">Deprecated</strong>
+ {</div>
+ }</div>
+ </div>
+</div>
diff --git a/tests/package/generic/data/attribute_formatting_005.xml
b/tests/package/generic/data/attribute_formatting_005.xml
new file mode 100644
index 000000000..c296e7fa5
--- /dev/null
+++ b/tests/package/generic/data/attribute_formatting_005.xml
@@ -0,0 +1,69 @@
+<chapter xml:id="attribute-formatting-005">
+ <section>
+ <para>1. Attribute with literal named arguments</para>
+ <classsynopsis class="class">
+ <ooclass>
+ <modifier role="attribute">#[\Deprecated(since: '8.5', message:
'Deprecated since PHP 8.4')]</modifier>
+ <modifier>final</modifier>
+ <classname>Deprecated</classname>
+ </ooclass>
+ </classsynopsis>
+ </section>
+
+ <section>
+ <para>2. Attribute with single literal positional argument</para>
+ <classsynopsis class="class">
+ <ooclass>
+ <modifier role="attribute">#[\Deprecated('Deprecated since PHP
8.4')]</modifier>
+ <modifier>final</modifier>
+ <classname>Deprecated</classname>
+ </ooclass>
+ </classsynopsis>
+ </section>
+
+ <section>
+ <para>3. Unknown attribute with literal argument</para>
+ <classsynopsis class="class">
+ <ooclass>
+ <modifier role="attribute">#[\UnknownAttribute(foo:
'bar')]</modifier>
+ <modifier>final</modifier>
+ <classname>Deprecated</classname>
+ </ooclass>
+ </classsynopsis>
+ </section>
+
+ <section>
+ <para>4. Namespaced attribute with literal argument</para>
+ <classsynopsis class="class">
+ <ooclass>
+ <modifier role="attribute">#[\Some\Namespaced\Attribute(value:
42)]</modifier>
+ <modifier>final</modifier>
+ <classname>Deprecated</classname>
+ </ooclass>
+ </classsynopsis>
+ </section>
+
+ <section>
+ <para>5. Multi-line attribute with literal class constant arguments</para>
+ <classsynopsis class="class">
+ <ooclass>
+ <modifier role="attribute">
+ #[\Attribute(Attribute::TARGET_FUNCTION|Attribute::TARGET_METHOD|Attribute::TARGET_CLASS)]
+ </modifier>
+ <modifier>final</modifier>
+ <classname>Deprecated</classname>
+ </ooclass>
+ </classsynopsis>
+ </section>
+
+ <section>
+ <para>6. Attribute with mix of known and unknown class constants</para>
+ <classsynopsis class="class">
+ <ooclass>
+ <modifier role="attribute">#[\Attribute(Attribute::TARGET_CLASS |
Unknown::CONST)]</modifier>
+ <modifier>final</modifier>
+ <classname>Deprecated</classname>
+ </ooclass>
+ </classsynopsis>
+ </section>
+</chapter>