[doc-en] master: Document attribute behavior for class loading and errors (#6043)
| From: | Jérôme Tamarelle via GitHub | Date: | Sun, 08 Nov 2026 16:47:46 +0000 |
| Subject: | [doc-en] master: Document attribute behavior for class loading and errors (#6043) | ||
| Groups: | php.doc.cvs | ||
| Request: | Send a blank email to doc-cvs+get-23922@lists.php.net to get a copy of this message | ||
Author: Jérôme Tamarelle (GromNaN)
Committer: GitHub (web-flow)
Pusher: lacatoire
Date: 2026-11-08T17:47:43+01:00
Commit: https://github.com/php/doc-en/commit/c89ca8abb0d92a3ba4c2fb9720f61d47f07515d1
Raw diff: https://github.com/php/doc-en/commit/c89ca8abb0d92a3ba4c2fb9720f61d47f07515d1.diff
Document attribute behavior for class loading and errors (#6043)
* Document attribute class loading with IS_INSTANCEOF
When IS_INSTANCEOF is used, PHP autoloads the filter class and every attribute
class on the element, and propagates errors thrown while loading them.
* Document ReflectionAttribute::getArguments()
The arguments are constant expressions evaluated when the method is called.
Evaluation can autoload classes, errors are propagated, and positional and
named arguments use different array keys.
* Document ReflectionAttribute::getName()
The name is resolved when the attribute is declared and returned as a fully
qualified class name without a leading backslash. The attribute class does
not need to exist.
* Document ReflectionAttribute::newInstance() errors
Cover a class that cannot be loaded, a class without the #[Attribute] marker,
a repeated attribute without IS_REPEATABLE, an invalid target, and errors
thrown by the constructor.
* Document that getAttributes() does not autoload without IS_INSTANCEOF
The name filter is a case-insensitive string comparison against the fully
qualified class name, and the class is not checked for existence.
* Clarify attribute name resolution
Only attribute names are resolved to classes. The arguments are evaluated and
passed to the constructor when an instance is requested.
* Document attribute inheritance and promoted parameters
Class attributes are not inherited, while attributes on inherited members are
returned when the member is reflected. An attribute on a promoted constructor
parameter is returned on both the parameter and the property.
* Update language/attributes.xml
---------
Co-authored-by: Louis-Arnaud <la.catoire@gmail.com>
Changed paths:
M language-snippets.ent
M language/attributes.xml
M reference/reflection/reflectionattribute.xml
M reference/reflection/reflectionattribute/getarguments.xml
M reference/reflection/reflectionattribute/getname.xml
M reference/reflection/reflectionattribute/newinstance.xml
Diff:
diff --git a/language-snippets.ent b/language-snippets.ent
index 7cf8ff649fa6..1693ca100919 100644
--- a/language-snippets.ent
+++ b/language-snippets.ent
@@ -2570,9 +2570,34 @@ to be references, then they must be references in the passed argument
list.'>
Filter the results to include only <classname>ReflectionAttribute</classname>
instances for attributes matching this class name.
</simpara>
+ <simpara>
+ Unless <constant>ReflectionAttribute::IS_INSTANCEOF</constant> is passed in
+ <parameter>flags</parameter>, no attribute class is autoloaded. The name is
+ compared case-insensitively with the fully qualified class name of each
+ attribute, and the class is not checked for existence.
+ </simpara>
</listitem>
</varlistentry>'>
+<!ENTITY reflection.getattributes.is-instanceof.note '<note xmlns="http://docbook.org/ns/docbook">
+ <simpara>
+ When <constant>ReflectionAttribute::IS_INSTANCEOF</constant> is used, the
+ class name specified by <parameter>name</parameter> is autoloaded before
+ any attribute is examined. If it cannot be loaded, the error or exception
+ thrown while loading it is propagated. If the class does not exist, an
+ <exceptionname>Error</exceptionname> is thrown.
+ </simpara>
+ <simpara>
+ PHP also autoloads the class of every attribute declared on the element
+ being reflected, including attributes that do not match the filter, because
+ it uses <literal>instanceof</literal> to filter them. An attribute whose class
+ does not exist is ignored. If an error or exception is thrown while loading
+ an attribute class, for example because a parent class is missing or its
+ file contains a syntax error, it is propagated, even if the attribute does
+ not match the filter.
+ </simpara>
+</note>'>
+
<!ENTITY reflection.getattributes.param.flags '<varlistentry xmlns="http://docbook.org/ns/docbook">
<term><parameter>flags</parameter></term>
<listitem>
@@ -2588,6 +2613,7 @@ to be references, then they must be references in the passed argument
list.'>
The only other option available, is to use
<constant>ReflectionAttribute::IS_INSTANCEOF</constant>,
which will instead use <literal>instanceof</literal> for filtering.
</simpara>
+ &reflection.getattributes.is-instanceof.note;
</listitem>
</varlistentry>'>
diff --git a/language/attributes.xml b/language/attributes.xml
index 81f1796c71b3..7b294b72b9f1 100644
--- a/language/attributes.xml
+++ b/language/attributes.xml
@@ -111,12 +111,12 @@ executeAction($copyAction);
expressions. Both positional and named argument syntax are supported.
</para>
- <para>
- Attribute names and their arguments are resolved to a class, and the arguments
- are passed to its constructor when an instance of the attribute is requested
+ <simpara>
+ An attribute name is resolved to a class, and the arguments are evaluated and
+ passed to the constructor when an instance of the attribute is requested
through the Reflection API. Therefore, it is recommended to introduce a class
for each attribute.
- </para>
+ </simpara>
<example>
<title>Attribute Syntax</title>
@@ -188,6 +188,22 @@ class AnotherThing
occurs at that point.
</para>
+ <simpara>
+ Class attributes are not inherited. Reflecting a subclass does not return
+ attributes declared on its parent class, its interfaces, or its traits.
+ Attributes on a property, method, or class constant are returned when that
+ member is reflected, including inherited members and trait members.
+ </simpara>
+
+ <simpara>
+ An attribute declared on a
+ <link linkend="language.oop5.decon.constructor.promotion">promoted constructor
+ parameter</link> is returned by <function>getAttributes</function> on both
the
+ <classname>ReflectionParameter</classname> and the corresponding
+ <classname>ReflectionProperty</classname>. An internal attribute that
+ cannot target one of them is only returned on the other.
+ </simpara>
+
<example>
<title>Reading Attributes using Reflection API</title>
diff --git a/reference/reflection/reflectionattribute.xml
b/reference/reflection/reflectionattribute.xml
index f938d74fd9f7..ee0d26170fcb 100644
--- a/reference/reflection/reflectionattribute.xml
+++ b/reference/reflection/reflectionattribute.xml
@@ -91,6 +91,7 @@
the given class or a subclass of it, or implements the given
interface, instead of only matching the exact class name.
</simpara>
+ &reflection.getattributes.is-instanceof.note;
</listitem>
</varlistentry>
diff --git a/reference/reflection/reflectionattribute/getarguments.xml
b/reference/reflection/reflectionattribute/getarguments.xml
index c10999b3965d..9d2c3732f8b6 100644
--- a/reference/reflection/reflectionattribute/getarguments.xml
+++ b/reference/reflection/reflectionattribute/getarguments.xml
@@ -3,7 +3,7 @@
<refentry xml:id="reflectionattribute.getarguments" xmlns="http://docbook.org/ns/docbook"
xmlns:xlink="http://www.w3.org/1999/xlink">
<refnamediv>
<refname>ReflectionAttribute::getArguments</refname>
- <refpurpose>Gets arguments passed to attribute</refpurpose>
+ <refpurpose>Gets the arguments passed to the attribute</refpurpose>
</refnamediv>
<refsect1 role="description">
@@ -12,9 +12,17 @@
<modifier>public</modifier>
<type>array</type><methodname>ReflectionAttribute::getArguments</methodname>
<void/>
</methodsynopsis>
- <para>
- Gets arguments passed to attribute.
- </para>
+ <simpara>
+ Gets the arguments passed to the attribute.
+ </simpara>
+ <simpara>
+ The arguments are constant expressions and are evaluated when this method
+ is called. Evaluating an argument may autoload a class referenced by the
+ expression, for example through a class constant, an enum case, or an
+ object created with <literal>new</literal>. Reading the arguments does not
+ require the attribute class to exist or validate them against the
+ constructor.
+ </simpara>
</refsect1>
@@ -25,9 +33,19 @@
<refsect1 role="returnvalues">
&reftitle.returnvalues;
- <para>
- The arguments passed to attribute.
- </para>
+ <simpara>
+ The arguments passed to the attribute, with integer keys for positional
+ arguments and string keys for named arguments.
+ </simpara>
+ </refsect1>
+
+ <refsect1 role="errors">
+ &reftitle.errors;
+ <simpara>
+ If evaluating an argument throws an error or exception, it is propagated.
+ This can happen when the argument references a class that cannot be loaded,
+ or a constant that is not defined.
+ </simpara>
</refsect1>
</refentry>
diff --git a/reference/reflection/reflectionattribute/getname.xml
b/reference/reflection/reflectionattribute/getname.xml
index df4e9ae018ff..904aa4c7c2a0 100644
--- a/reference/reflection/reflectionattribute/getname.xml
+++ b/reference/reflection/reflectionattribute/getname.xml
@@ -3,7 +3,7 @@
<refentry xml:id="reflectionattribute.getname" xmlns="http://docbook.org/ns/docbook"
xmlns:xlink="http://www.w3.org/1999/xlink">
<refnamediv>
<refname>ReflectionAttribute::getName</refname>
- <refpurpose>Gets attribute name</refpurpose>
+ <refpurpose>Gets the name of the attribute</refpurpose>
</refnamediv>
<refsect1 role="description">
@@ -12,9 +12,15 @@
<modifier>public</modifier>
<type>string</type><methodname>ReflectionAttribute::getName</methodname>
<void/>
</methodsynopsis>
- <para>
- Gets the attributes name.
- </para>
+ <simpara>
+ Gets the name of the attribute.
+ </simpara>
+ <simpara>
+ The name is resolved when the attribute is declared, using the imports and
+ namespace in effect at that point. The returned name is the fully qualified
+ class name of the attribute, without a leading backslash. The attribute class
+ does not need to exist for the name to be returned.
+ </simpara>
</refsect1>
diff --git a/reference/reflection/reflectionattribute/newinstance.xml
b/reference/reflection/reflectionattribute/newinstance.xml
index 3af067b1d399..f57f2af779d3 100644
--- a/reference/reflection/reflectionattribute/newinstance.xml
+++ b/reference/reflection/reflectionattribute/newinstance.xml
@@ -31,12 +31,22 @@
<refsect1 role="errors">
&reftitle.errors;
+ <simpara>
+ Throws an <exceptionname>Error</exceptionname> if the attribute class cannot
+ be loaded, or if the class is not declared with
+ <literal>#[Attribute]</literal>.
+ </simpara>
<simpara>
Throws an <exceptionname>Error</exceptionname> if the attribute is applied
- to a target it cannot be used on. For internal attributes, this happens
- here instead of at compile time when the declaration uses
+ to a target it cannot be used on, or if it is applied more than once and is
+ not declared with <constant>Attribute::IS_REPEATABLE</constant>. For
+ internal attributes, target validation happens here instead of at compile
+ time when the declaration uses
<link
linkend="class.delayedtargetvalidation">#[\DelayedTargetValidation]</link>.
</simpara>
+ <simpara>
+ Errors and exceptions thrown by the attribute constructor are propagated.
+ </simpara>
</refsect1>
<refsect1 role="changelog">