Commit 4923c6079e5 for php.net
commit 4923c6079e5b09d3e48e8499ff62348fd56e3a9d
Author: Daniel Scherzer <daniel.e.scherzer@gmail.com>
Date: Mon Aug 31 14:03:55 2026 -0700
[RFC] Deprecate `is_a()` and `is_subclass_of()` with string when not allowed (#23236)
For `is_a()` and `is_subclass_of()`, emit deprecation warnings when calling
with a string as the first argument but with `$allow_string` being false. The
two functions share implementation code and so are updated together.
https://wiki.php.net/rfc/deprecations_php_8_6
diff --git a/Zend/tests/is_a_string.phpt b/Zend/tests/is_a_string.phpt
new file mode 100644
index 00000000000..3334cfb1596
--- /dev/null
+++ b/Zend/tests/is_a_string.phpt
@@ -0,0 +1,16 @@
+--TEST--
+is_a() with $allow_string false and a string
+--FILE--
+<?php
+
+class Demo {}
+
+var_dump(is_a(Demo::class, Demo::class, true));
+var_dump(is_a(Demo::class, Demo::class, false));
+
+?>
+--EXPECTF--
+bool(true)
+
+Deprecated: Calling is_a() with a string when $allow_string is false in %s on line %d
+bool(false)
diff --git a/Zend/tests/is_subclass_of_string.phpt b/Zend/tests/is_subclass_of_string.phpt
new file mode 100644
index 00000000000..5ef3407b1dc
--- /dev/null
+++ b/Zend/tests/is_subclass_of_string.phpt
@@ -0,0 +1,18 @@
+--TEST--
+is_subclass_of() with $allow_string false and a string
+--FILE--
+<?php
+
+class Demo {}
+
+class Child extends Demo {}
+
+var_dump(is_subclass_of(Child::class, Demo::class, true));
+var_dump(is_subclass_of(Child::class, Demo::class, false));
+
+?>
+--EXPECTF--
+bool(true)
+
+Deprecated: Calling is_subclass_of() with a string when $allow_string is false in %s on line %d
+bool(false)
diff --git a/Zend/zend_builtin_functions.c b/Zend/zend_builtin_functions.c
index 9aa93042238..e02e0afe2bf 100644
--- a/Zend/zend_builtin_functions.c
+++ b/Zend/zend_builtin_functions.c
@@ -714,6 +714,18 @@ static void is_a_impl(INTERNAL_FUNCTION_PARAMETERS, bool only_subclass) /* {{{ *
if (!instance_ce) {
RETURN_FALSE;
}
+ } else if (Z_TYPE_P(obj) == IS_STRING) {
+ // is_a() uses only_subclass as false, is_subclass_of() uses it as true
+ zend_error(
+ E_DEPRECATED,
+ only_subclass
+ ? "Calling is_subclass_of() with a string when $allow_string is false"
+ : "Calling is_a() with a string when $allow_string is false"
+ );
+ if (UNEXPECTED(EG(exception))) {
+ RETURN_THROWS();
+ }
+ RETURN_FALSE;
} else if (Z_TYPE_P(obj) == IS_OBJECT) {
instance_ce = Z_OBJCE_P(obj);
} else {
diff --git a/ext/standard/tests/class_object/is_a.phpt b/ext/standard/tests/class_object/is_a.phpt
index efb03b34349..ee6f30455a6 100644
--- a/ext/standard/tests/class_object/is_a.phpt
+++ b/ext/standard/tests/class_object/is_a.phpt
@@ -81,109 +81,173 @@ class derived_d extends derived_c {
?>
---EXPECT--
+--EXPECTF--
>>> With Defined class
is_a( OBJECT:base, base) = yes
+
+Deprecated: Calling is_a() with a string when $allow_string is false in %s on line %d
is_a( STRING:base, base) = no
is_a( STRING:base, base, true) = yes
is_subclass_of( OBJECT:base, base) = no
is_subclass_of( STRING:base, base) = no
+
+Deprecated: Calling is_subclass_of() with a string when $allow_string is false in %s on line %d
is_subclass_of( STRING:base, base,false) = no
>>> With Undefined
is_a( STRING:undefB, base,true) = no
+
+Deprecated: Calling is_a() with a string when $allow_string is false in %s on line %d
is_a( STRING:undefB, base) = no
+
+Deprecated: Calling is_subclass_of() with a string when $allow_string is false in %s on line %d
is_subclass_of( STRING:undefB, base,false) = no
is_subclass_of( STRING:undefB, base) = no
>>> With Defined class
is_a( OBJECT:base, derived_a) = no
+
+Deprecated: Calling is_a() with a string when $allow_string is false in %s on line %d
is_a( STRING:base, derived_a) = no
is_a( STRING:base, derived_a, true) = no
is_subclass_of( OBJECT:base, derived_a) = no
is_subclass_of( STRING:base, derived_a) = no
+
+Deprecated: Calling is_subclass_of() with a string when $allow_string is false in %s on line %d
is_subclass_of( STRING:base, derived_a,false) = no
>>> With Undefined
is_a( STRING:undefB, derived_a,true) = no
+
+Deprecated: Calling is_a() with a string when $allow_string is false in %s on line %d
is_a( STRING:undefB, derived_a) = no
+
+Deprecated: Calling is_subclass_of() with a string when $allow_string is false in %s on line %d
is_subclass_of( STRING:undefB, derived_a,false) = no
is_subclass_of( STRING:undefB, derived_a) = no
>>> With Defined class
is_a( OBJECT:base, if_a) = no
+
+Deprecated: Calling is_a() with a string when $allow_string is false in %s on line %d
is_a( STRING:base, if_a) = no
is_a( STRING:base, if_a, true) = no
is_subclass_of( OBJECT:base, if_a) = no
is_subclass_of( STRING:base, if_a) = no
+
+Deprecated: Calling is_subclass_of() with a string when $allow_string is false in %s on line %d
is_subclass_of( STRING:base, if_a,false) = no
>>> With Undefined
is_a( STRING:undefB, if_a,true) = no
+
+Deprecated: Calling is_a() with a string when $allow_string is false in %s on line %d
is_a( STRING:undefB, if_a) = no
+
+Deprecated: Calling is_subclass_of() with a string when $allow_string is false in %s on line %d
is_subclass_of( STRING:undefB, if_a,false) = no
is_subclass_of( STRING:undefB, if_a) = no
>>> With Defined class
is_a( OBJECT:base, undefA) = no
+
+Deprecated: Calling is_a() with a string when $allow_string is false in %s on line %d
is_a( STRING:base, undefA) = no
is_a( STRING:base, undefA, true) = no
is_subclass_of( OBJECT:base, undefA) = no
is_subclass_of( STRING:base, undefA) = no
+
+Deprecated: Calling is_subclass_of() with a string when $allow_string is false in %s on line %d
is_subclass_of( STRING:base, undefA,false) = no
>>> With Undefined
is_a( STRING:undefB, undefA,true) = no
+
+Deprecated: Calling is_a() with a string when $allow_string is false in %s on line %d
is_a( STRING:undefB, undefA) = no
+
+Deprecated: Calling is_subclass_of() with a string when $allow_string is false in %s on line %d
is_subclass_of( STRING:undefB, undefA,false) = no
is_subclass_of( STRING:undefB, undefA) = no
>>> With Defined class
is_a( OBJECT:derived_a, base) = yes
+
+Deprecated: Calling is_a() with a string when $allow_string is false in %s on line %d
is_a( STRING:derived_a, base) = no
is_a( STRING:derived_a, base, true) = yes
is_subclass_of( OBJECT:derived_a, base) = yes
is_subclass_of( STRING:derived_a, base) = yes
+
+Deprecated: Calling is_subclass_of() with a string when $allow_string is false in %s on line %d
is_subclass_of( STRING:derived_a, base,false) = no
>>> With Undefined
is_a( STRING:undefB, base,true) = no
+
+Deprecated: Calling is_a() with a string when $allow_string is false in %s on line %d
is_a( STRING:undefB, base) = no
+
+Deprecated: Calling is_subclass_of() with a string when $allow_string is false in %s on line %d
is_subclass_of( STRING:undefB, base,false) = no
is_subclass_of( STRING:undefB, base) = no
>>> With Defined class
is_a( OBJECT:derived_a, derived_a) = yes
+
+Deprecated: Calling is_a() with a string when $allow_string is false in %s on line %d
is_a( STRING:derived_a, derived_a) = no
is_a( STRING:derived_a, derived_a, true) = yes
is_subclass_of( OBJECT:derived_a, derived_a) = no
is_subclass_of( STRING:derived_a, derived_a) = no
+
+Deprecated: Calling is_subclass_of() with a string when $allow_string is false in %s on line %d
is_subclass_of( STRING:derived_a, derived_a,false) = no
>>> With Undefined
is_a( STRING:undefB, derived_a,true) = no
+
+Deprecated: Calling is_a() with a string when $allow_string is false in %s on line %d
is_a( STRING:undefB, derived_a) = no
+
+Deprecated: Calling is_subclass_of() with a string when $allow_string is false in %s on line %d
is_subclass_of( STRING:undefB, derived_a,false) = no
is_subclass_of( STRING:undefB, derived_a) = no
>>> With Defined class
is_a( OBJECT:derived_a, if_a) = yes
+
+Deprecated: Calling is_a() with a string when $allow_string is false in %s on line %d
is_a( STRING:derived_a, if_a) = no
is_a( STRING:derived_a, if_a, true) = yes
is_subclass_of( OBJECT:derived_a, if_a) = yes
is_subclass_of( STRING:derived_a, if_a) = yes
+
+Deprecated: Calling is_subclass_of() with a string when $allow_string is false in %s on line %d
is_subclass_of( STRING:derived_a, if_a,false) = no
>>> With Undefined
is_a( STRING:undefB, if_a,true) = no
+
+Deprecated: Calling is_a() with a string when $allow_string is false in %s on line %d
is_a( STRING:undefB, if_a) = no
+
+Deprecated: Calling is_subclass_of() with a string when $allow_string is false in %s on line %d
is_subclass_of( STRING:undefB, if_a,false) = no
is_subclass_of( STRING:undefB, if_a) = no
>>> With Defined class
is_a( OBJECT:derived_a, undefA) = no
+
+Deprecated: Calling is_a() with a string when $allow_string is false in %s on line %d
is_a( STRING:derived_a, undefA) = no
is_a( STRING:derived_a, undefA, true) = no
is_subclass_of( OBJECT:derived_a, undefA) = no
is_subclass_of( STRING:derived_a, undefA) = no
+
+Deprecated: Calling is_subclass_of() with a string when $allow_string is false in %s on line %d
is_subclass_of( STRING:derived_a, undefA,false) = no
>>> With Undefined
is_a( STRING:undefB, undefA,true) = no
+
+Deprecated: Calling is_a() with a string when $allow_string is false in %s on line %d
is_a( STRING:undefB, undefA) = no
+
+Deprecated: Calling is_subclass_of() with a string when $allow_string is false in %s on line %d
is_subclass_of( STRING:undefB, undefA,false) = no
is_subclass_of( STRING:undefB, undefA) = no
@@ -192,60 +256,92 @@ class derived_d extends derived_c {
>>> With Defined class
is_a( OBJECT:base, base) = yes
+
+Deprecated: Calling is_a() with a string when $allow_string is false in %s on line %d
is_a( STRING:base, base) = no
is_a( STRING:base, base, true) = yes
is_subclass_of( OBJECT:base, base) = no
is_subclass_of( STRING:base, base) = no
+
+Deprecated: Calling is_subclass_of() with a string when $allow_string is false in %s on line %d
is_subclass_of( STRING:base, base,false) = no
>>> With Undefined
>>>> In autoload: string(6) "undefB"
is_a( STRING:undefB, base,true) = no
+
+Deprecated: Calling is_a() with a string when $allow_string is false in %s on line %d
is_a( STRING:undefB, base) = no
+
+Deprecated: Calling is_subclass_of() with a string when $allow_string is false in %s on line %d
is_subclass_of( STRING:undefB, base,false) = no
>>>> In autoload: string(6) "undefB"
is_subclass_of( STRING:undefB, base) = no
>>> With Defined class
is_a( OBJECT:base, derived_a) = no
+
+Deprecated: Calling is_a() with a string when $allow_string is false in %s on line %d
is_a( STRING:base, derived_a) = no
is_a( STRING:base, derived_a, true) = no
is_subclass_of( OBJECT:base, derived_a) = no
is_subclass_of( STRING:base, derived_a) = no
+
+Deprecated: Calling is_subclass_of() with a string when $allow_string is false in %s on line %d
is_subclass_of( STRING:base, derived_a,false) = no
>>> With Undefined
>>>> In autoload: string(6) "undefB"
is_a( STRING:undefB, derived_a,true) = no
+
+Deprecated: Calling is_a() with a string when $allow_string is false in %s on line %d
is_a( STRING:undefB, derived_a) = no
+
+Deprecated: Calling is_subclass_of() with a string when $allow_string is false in %s on line %d
is_subclass_of( STRING:undefB, derived_a,false) = no
>>>> In autoload: string(6) "undefB"
is_subclass_of( STRING:undefB, derived_a) = no
>>> With Defined class
is_a( OBJECT:base, if_a) = no
+
+Deprecated: Calling is_a() with a string when $allow_string is false in %s on line %d
is_a( STRING:base, if_a) = no
is_a( STRING:base, if_a, true) = no
is_subclass_of( OBJECT:base, if_a) = no
is_subclass_of( STRING:base, if_a) = no
+
+Deprecated: Calling is_subclass_of() with a string when $allow_string is false in %s on line %d
is_subclass_of( STRING:base, if_a,false) = no
>>> With Undefined
>>>> In autoload: string(6) "undefB"
is_a( STRING:undefB, if_a,true) = no
+
+Deprecated: Calling is_a() with a string when $allow_string is false in %s on line %d
is_a( STRING:undefB, if_a) = no
+
+Deprecated: Calling is_subclass_of() with a string when $allow_string is false in %s on line %d
is_subclass_of( STRING:undefB, if_a,false) = no
>>>> In autoload: string(6) "undefB"
is_subclass_of( STRING:undefB, if_a) = no
>>> With Defined class
is_a( OBJECT:base, undefA) = no
+
+Deprecated: Calling is_a() with a string when $allow_string is false in %s on line %d
is_a( STRING:base, undefA) = no
is_a( STRING:base, undefA, true) = no
is_subclass_of( OBJECT:base, undefA) = no
is_subclass_of( STRING:base, undefA) = no
+
+Deprecated: Calling is_subclass_of() with a string when $allow_string is false in %s on line %d
is_subclass_of( STRING:base, undefA,false) = no
>>> With Undefined
>>>> In autoload: string(6) "undefB"
is_a( STRING:undefB, undefA,true) = no
+
+Deprecated: Calling is_a() with a string when $allow_string is false in %s on line %d
is_a( STRING:undefB, undefA) = no
+
+Deprecated: Calling is_subclass_of() with a string when $allow_string is false in %s on line %d
is_subclass_of( STRING:undefB, undefA,false) = no
>>>> In autoload: string(6) "undefB"
is_subclass_of( STRING:undefB, undefA) = no
@@ -253,60 +349,92 @@ class derived_d extends derived_c {
>>> With Defined class
is_a( OBJECT:derived_a, base) = yes
+
+Deprecated: Calling is_a() with a string when $allow_string is false in %s on line %d
is_a( STRING:derived_a, base) = no
is_a( STRING:derived_a, base, true) = yes
is_subclass_of( OBJECT:derived_a, base) = yes
is_subclass_of( STRING:derived_a, base) = yes
+
+Deprecated: Calling is_subclass_of() with a string when $allow_string is false in %s on line %d
is_subclass_of( STRING:derived_a, base,false) = no
>>> With Undefined
>>>> In autoload: string(6) "undefB"
is_a( STRING:undefB, base,true) = no
+
+Deprecated: Calling is_a() with a string when $allow_string is false in %s on line %d
is_a( STRING:undefB, base) = no
+
+Deprecated: Calling is_subclass_of() with a string when $allow_string is false in %s on line %d
is_subclass_of( STRING:undefB, base,false) = no
>>>> In autoload: string(6) "undefB"
is_subclass_of( STRING:undefB, base) = no
>>> With Defined class
is_a( OBJECT:derived_a, derived_a) = yes
+
+Deprecated: Calling is_a() with a string when $allow_string is false in %s on line %d
is_a( STRING:derived_a, derived_a) = no
is_a( STRING:derived_a, derived_a, true) = yes
is_subclass_of( OBJECT:derived_a, derived_a) = no
is_subclass_of( STRING:derived_a, derived_a) = no
+
+Deprecated: Calling is_subclass_of() with a string when $allow_string is false in %s on line %d
is_subclass_of( STRING:derived_a, derived_a,false) = no
>>> With Undefined
>>>> In autoload: string(6) "undefB"
is_a( STRING:undefB, derived_a,true) = no
+
+Deprecated: Calling is_a() with a string when $allow_string is false in %s on line %d
is_a( STRING:undefB, derived_a) = no
+
+Deprecated: Calling is_subclass_of() with a string when $allow_string is false in %s on line %d
is_subclass_of( STRING:undefB, derived_a,false) = no
>>>> In autoload: string(6) "undefB"
is_subclass_of( STRING:undefB, derived_a) = no
>>> With Defined class
is_a( OBJECT:derived_a, if_a) = yes
+
+Deprecated: Calling is_a() with a string when $allow_string is false in %s on line %d
is_a( STRING:derived_a, if_a) = no
is_a( STRING:derived_a, if_a, true) = yes
is_subclass_of( OBJECT:derived_a, if_a) = yes
is_subclass_of( STRING:derived_a, if_a) = yes
+
+Deprecated: Calling is_subclass_of() with a string when $allow_string is false in %s on line %d
is_subclass_of( STRING:derived_a, if_a,false) = no
>>> With Undefined
>>>> In autoload: string(6) "undefB"
is_a( STRING:undefB, if_a,true) = no
+
+Deprecated: Calling is_a() with a string when $allow_string is false in %s on line %d
is_a( STRING:undefB, if_a) = no
+
+Deprecated: Calling is_subclass_of() with a string when $allow_string is false in %s on line %d
is_subclass_of( STRING:undefB, if_a,false) = no
>>>> In autoload: string(6) "undefB"
is_subclass_of( STRING:undefB, if_a) = no
>>> With Defined class
is_a( OBJECT:derived_a, undefA) = no
+
+Deprecated: Calling is_a() with a string when $allow_string is false in %s on line %d
is_a( STRING:derived_a, undefA) = no
is_a( STRING:derived_a, undefA, true) = no
is_subclass_of( OBJECT:derived_a, undefA) = no
is_subclass_of( STRING:derived_a, undefA) = no
+
+Deprecated: Calling is_subclass_of() with a string when $allow_string is false in %s on line %d
is_subclass_of( STRING:derived_a, undefA,false) = no
>>> With Undefined
>>>> In autoload: string(6) "undefB"
is_a( STRING:undefB, undefA,true) = no
+
+Deprecated: Calling is_a() with a string when $allow_string is false in %s on line %d
is_a( STRING:undefB, undefA) = no
+
+Deprecated: Calling is_subclass_of() with a string when $allow_string is false in %s on line %d
is_subclass_of( STRING:undefB, undefA,false) = no
>>>> In autoload: string(6) "undefB"
is_subclass_of( STRING:undefB, undefA) = no
@@ -314,60 +442,92 @@ class derived_d extends derived_c {
>>> With Defined class
is_a( OBJECT:derived_b, base) = yes
+
+Deprecated: Calling is_a() with a string when $allow_string is false in %s on line %d
is_a( STRING:derived_b, base) = no
is_a( STRING:derived_b, base, true) = yes
is_subclass_of( OBJECT:derived_b, base) = yes
is_subclass_of( STRING:derived_b, base) = yes
+
+Deprecated: Calling is_subclass_of() with a string when $allow_string is false in %s on line %d
is_subclass_of( STRING:derived_b, base,false) = no
>>> With Undefined
>>>> In autoload: string(6) "undefB"
is_a( STRING:undefB, base,true) = no
+
+Deprecated: Calling is_a() with a string when $allow_string is false in %s on line %d
is_a( STRING:undefB, base) = no
+
+Deprecated: Calling is_subclass_of() with a string when $allow_string is false in %s on line %d
is_subclass_of( STRING:undefB, base,false) = no
>>>> In autoload: string(6) "undefB"
is_subclass_of( STRING:undefB, base) = no
>>> With Defined class
is_a( OBJECT:derived_b, derived_a) = no
+
+Deprecated: Calling is_a() with a string when $allow_string is false in %s on line %d
is_a( STRING:derived_b, derived_a) = no
is_a( STRING:derived_b, derived_a, true) = no
is_subclass_of( OBJECT:derived_b, derived_a) = no
is_subclass_of( STRING:derived_b, derived_a) = no
+
+Deprecated: Calling is_subclass_of() with a string when $allow_string is false in %s on line %d
is_subclass_of( STRING:derived_b, derived_a,false) = no
>>> With Undefined
>>>> In autoload: string(6) "undefB"
is_a( STRING:undefB, derived_a,true) = no
+
+Deprecated: Calling is_a() with a string when $allow_string is false in %s on line %d
is_a( STRING:undefB, derived_a) = no
+
+Deprecated: Calling is_subclass_of() with a string when $allow_string is false in %s on line %d
is_subclass_of( STRING:undefB, derived_a,false) = no
>>>> In autoload: string(6) "undefB"
is_subclass_of( STRING:undefB, derived_a) = no
>>> With Defined class
is_a( OBJECT:derived_b, if_a) = yes
+
+Deprecated: Calling is_a() with a string when $allow_string is false in %s on line %d
is_a( STRING:derived_b, if_a) = no
is_a( STRING:derived_b, if_a, true) = yes
is_subclass_of( OBJECT:derived_b, if_a) = yes
is_subclass_of( STRING:derived_b, if_a) = yes
+
+Deprecated: Calling is_subclass_of() with a string when $allow_string is false in %s on line %d
is_subclass_of( STRING:derived_b, if_a,false) = no
>>> With Undefined
>>>> In autoload: string(6) "undefB"
is_a( STRING:undefB, if_a,true) = no
+
+Deprecated: Calling is_a() with a string when $allow_string is false in %s on line %d
is_a( STRING:undefB, if_a) = no
+
+Deprecated: Calling is_subclass_of() with a string when $allow_string is false in %s on line %d
is_subclass_of( STRING:undefB, if_a,false) = no
>>>> In autoload: string(6) "undefB"
is_subclass_of( STRING:undefB, if_a) = no
>>> With Defined class
is_a( OBJECT:derived_b, undefA) = no
+
+Deprecated: Calling is_a() with a string when $allow_string is false in %s on line %d
is_a( STRING:derived_b, undefA) = no
is_a( STRING:derived_b, undefA, true) = no
is_subclass_of( OBJECT:derived_b, undefA) = no
is_subclass_of( STRING:derived_b, undefA) = no
+
+Deprecated: Calling is_subclass_of() with a string when $allow_string is false in %s on line %d
is_subclass_of( STRING:derived_b, undefA,false) = no
>>> With Undefined
>>>> In autoload: string(6) "undefB"
is_a( STRING:undefB, undefA,true) = no
+
+Deprecated: Calling is_a() with a string when $allow_string is false in %s on line %d
is_a( STRING:undefB, undefA) = no
+
+Deprecated: Calling is_subclass_of() with a string when $allow_string is false in %s on line %d
is_subclass_of( STRING:undefB, undefA,false) = no
>>>> In autoload: string(6) "undefB"
is_subclass_of( STRING:undefB, undefA) = no
diff --git a/ext/standard/tests/class_object/is_a_variation_001.phpt b/ext/standard/tests/class_object/is_a_variation_001.phpt
index 4f77b9735a7..374007da7b1 100644
--- a/ext/standard/tests/class_object/is_a_variation_001.phpt
+++ b/ext/standard/tests/class_object/is_a_variation_001.phpt
@@ -137,15 +137,23 @@
bool(false)
Arg value
+
+Deprecated: Calling is_a() with a string when $allow_string is false in %s on line %d
bool(false)
Arg value
+
+Deprecated: Calling is_a() with a string when $allow_string is false in %s on line %d
bool(false)
Arg value string
+
+Deprecated: Calling is_a() with a string when $allow_string is false in %s on line %d
bool(false)
Arg value String
+
+Deprecated: Calling is_a() with a string when $allow_string is false in %s on line %d
bool(false)
Arg value