Repository: php/php-src (2025-07-17T00:37:49Z)

Issue:
#[\Attribute] validation should error on trait/interface/enum/abstract class
### Description

See https://3v4l.org/99PhY
```php
<?php

#[Attribute]
enum DemoEnum {}

#[Attribute]
trait DemoTrait {}

#[Attribute]
abstract class DemoAbstract {}

#[Attribute]
interface DemoInterface {}

#[DemoEnum]
#[DemoTrait]
#[DemoAbstract]
#[DemoInterface]
function usesWrongAttrib() {}

$r = new ReflectionFunction( 'usesWrongAttrib' );
$a = $r->getAttributes();
var_dump( $a );
foreach ( $a as $attrib ) {
    try {
        $attrib->newInstance();
    } catch ( Error $e ) {
        echo get_class( $e ) . ": " . $e->getMessage() . "\n";
    }
}
```

currently outputs
```
array(4) {
  [0]=>
  object(ReflectionAttribute)#2 (1) {
    ["name"]=>
    string(8) "DemoEnum"
  }
  [1]=>
  object(ReflectionAttribute)#3 (1) {
    ["name"]=>
    string(9) "DemoTrait"
  }
  [2]=>
  object(ReflectionAttribute)#4 (1) {
    ["name"]=>
    string(12) "DemoAbstract"
  }
  [3]=>
  object(ReflectionAttribute)#5 (1) {
    ["name"]=>
    string(13) "DemoInterface"
  }
}
Error: Cannot instantiate enum DemoEnum
Error: Cannot instantiate trait DemoTrait
Error: Cannot instantiate abstract class DemoAbstract
Error: Cannot instantiate interface DemoInterface
```

But we should have compile-time errors instead.


Discussion:
Cross link #15731 which had similar reasoning



Patch:
diff --git a/NEWS b/NEWS
index 03711eb0a4691..fa0a98a01c6d9 100644
--- a/NEWS
+++ b/NEWS
@@ -62,6 +62,8 @@ PHP                                                                        NEWS
   . The socket_set_timeout() alias function has been deprecated. (timwolla)
   . Passing null to to readdir(), rewinddir(), and closedir() to use the last
     opened directory has been deprecated. (Girgias)
+  . Fixed bug GH-19153 (#[\Attribute] validation should error on
+    trait/interface/enum/abstract class). (DanielEScherzer)
 
 31 Jul 2025, PHP 8.5.0alpha4
 
diff --git a/Zend/zend_attributes.c b/Zend/zend_attributes.c
index 01b16d7d205eb..3256e220d8f3a 100644
--- a/Zend/zend_attributes.c
+++ b/Zend/zend_attributes.c
@@ -95,6 +95,24 @@ static void validate_allow_dynamic_properties(
 	scope->ce_flags |= ZEND_ACC_ALLOW_DYNAMIC_PROPERTIES;
 }
 
+static void validate_attribute(
+	zend_attribute *attr, uint32_t target, zend_class_entry *scope)
+{
+	const char *msg = NULL;
+	if (scope->ce_flags & ZEND_ACC_TRAIT) {
+		msg = "Cannot apply #[\\Attribute] to trait %s";
+	} else if (scope->ce_flags & ZEND_ACC_INTERFACE) {
+		msg = "Cannot apply #[\\Attribute] to interface %s";
+	} else if (scope->ce_flags & ZEND_ACC_ENUM) {
+		msg = "Cannot apply #[\\Attribute] to enum %s";
+	} else if (scope->ce_flags & ZEND_ACC_EXPLICIT_ABSTRACT_CLASS) {
+		msg = "Cannot apply #[\\Attribute] to abstract class %s";
+	}
+	if (msg != NULL) {
+		zend_error_noreturn(E_ERROR, msg, ZSTR_VAL(scope->name));
+	}
+}
+
 ZEND_METHOD(Attribute, __construct)
 {
 	zend_long flags = ZEND_ATTRIBUTE_TARGET_ALL;
@@ -522,6 +540,7 @@ void zend_register_attribute_ce(void)
 
 	zend_ce_attribute = register_class_Attribute();
 	attr = zend_mark_internal_attribute(zend_ce_attribute);
+	attr->validator = validate_attribute;
 
 	zend_ce_return_type_will_change_attribute = register_class_ReturnTypeWillChange();
 	zend_mark_internal_attribute(zend_ce_return_type_will_change_attribute);


Test patch:
diff --git a/Zend/tests/attributes/Attribute/Attribute_on_abstract.phpt b/Zend/tests/attributes/Attribute/Attribute_on_abstract.phpt
new file mode 100644
index 0000000000000..547946c4e1300
--- /dev/null
+++ b/Zend/tests/attributes/Attribute/Attribute_on_abstract.phpt
@@ -0,0 +1,12 @@
+--TEST--
+#[Attribute] on an abstract class
+--FILE--
+<?php
+
+#[Attribute]
+abstract class Demo {}
+
+echo "Done\n";
+?>
+--EXPECTF--
+Fatal error: Cannot apply #[\Attribute] to abstract class Demo in %s on line %d
diff --git a/Zend/tests/attributes/Attribute/Attribute_on_enum.phpt b/Zend/tests/attributes/Attribute/Attribute_on_enum.phpt
new file mode 100644
index 0000000000000..c5545054dcff3
--- /dev/null
+++ b/Zend/tests/attributes/Attribute/Attribute_on_enum.phpt
@@ -0,0 +1,12 @@
+--TEST--
+#[Attribute] on an enum
+--FILE--
+<?php
+
+#[Attribute]
+enum Demo {}
+
+echo "Done\n";
+?>
+--EXPECTF--
+Fatal error: Cannot apply #[\Attribute] to enum Demo in %s on line %d
diff --git a/Zend/tests/attributes/Attribute/Attribute_on_interface.phpt b/Zend/tests/attributes/Attribute/Attribute_on_interface.phpt
new file mode 100644
index 0000000000000..4b9a87a2395d8
--- /dev/null
+++ b/Zend/tests/attributes/Attribute/Attribute_on_interface.phpt
@@ -0,0 +1,12 @@
+--TEST--
+#[Attribute] on an interface
+--FILE--
+<?php
+
+#[Attribute]
+interface Demo {}
+
+echo "Done\n";
+?>
+--EXPECTF--
+Fatal error: Cannot apply #[\Attribute] to interface Demo in %s on line %d
diff --git a/Zend/tests/attributes/Attribute/Attribute_on_trait.phpt b/Zend/tests/attributes/Attribute/Attribute_on_trait.phpt
new file mode 100644
index 0000000000000..7ea05543baa29
--- /dev/null
+++ b/Zend/tests/attributes/Attribute/Attribute_on_trait.phpt
@@ -0,0 +1,12 @@
+--TEST--
+#[Attribute] on a trait
+--FILE--
+<?php
+
+#[Attribute]
+trait Demo {}
+
+echo "Done\n";
+?>
+--EXPECTF--
+Fatal error: Cannot apply #[\Attribute] to trait Demo in %s on line %d