Implement Stringable automatically for internal classes

Requiring all internal classes (including those from 3rd-party
extensions) to implement Stringable if they provide __toString()
is too error prone. Case in point, our _ZendTestClass test class
was not doing so, resulting in preloading test failures after
recent changes.

Instead we automatically implement Stringable, the same as we do
for userland classes. We still allow explicit implementations,
but ignore them (normally they would result in an error due to
duplicate interface implementation). Finally, we need to be
careful about not trying to implement Stringable on Stringable
itself.

In some cases this changes the interface order, in particular the
automatic Stringable implementation will now come first.
This commit is contained in:
Nikita Popov 2021-11-05 10:19:40 +01:00
parent d478ae73b1
commit b302bfabe7
4 changed files with 33 additions and 2 deletions

View file

@ -0,0 +1,16 @@
--TEST--
Stringable should be automatically implemented for internal classes
--SKIPIF--
<?php
if (!extension_loaded('zend-test')) die('skip');
?>
--FILE--
<?php
// _ZendTestClass defines __toString() but does not explicitly implement Stringable.
$obj = new _ZendTestClass;
var_dump($obj instanceof Stringable);
?>
--EXPECT--
bool(true)

View file

@ -2767,6 +2767,13 @@ static zend_class_entry *do_register_internal_class(zend_class_entry *orig_class
lowercase_name = zend_new_interned_string(lowercase_name);
zend_hash_update_ptr(CG(class_table), lowercase_name, class_entry);
zend_string_release_ex(lowercase_name, 1);
if (class_entry->__tostring && !zend_string_equals_literal(class_entry->name, "Stringable")
&& !(class_entry->ce_flags & ZEND_ACC_TRAIT)) {
ZEND_ASSERT(zend_ce_stringable
&& "Should be registered before first class using __toString()");
zend_do_implement_interface(class_entry, zend_ce_stringable);
}
return class_entry;
}
/* }}} */
@ -2786,6 +2793,7 @@ ZEND_API zend_class_entry *zend_register_internal_class_ex(zend_class_entry *cla
zend_do_inheritance(register_class, parent_ce);
zend_build_properties_info_table(register_class);
}
return register_class;
}
/* }}} */
@ -2798,6 +2806,13 @@ ZEND_API void zend_class_implements(zend_class_entry *class_entry, int num_inter
while (num_interfaces--) {
interface_entry = va_arg(interface_list, zend_class_entry *);
if (interface_entry == zend_ce_stringable
&& zend_class_implements_interface(class_entry, zend_ce_stringable)) {
/* Stringable is implemented automatically,
* silently ignore an explicit implementation. */
continue;
}
zend_do_implement_interface(class_entry, interface_entry);
}

View file

@ -9,7 +9,7 @@ $rc = new ReflectionClass("ReflectionClass");
echo $rc;
?>
--EXPECT--
Class [ <internal:Reflection> class ReflectionClass implements Reflector, Stringable ] {
Class [ <internal:Reflection> class ReflectionClass implements Stringable, Reflector ] {
- Constants [3] {
Constant [ public int IS_IMPLICIT_ABSTRACT ] { 16 }

View file

@ -37,7 +37,7 @@ string(183) "Class [ <internal:Core> class stdClass ] {
}
"
string(2194) "Class [ <internal:Core> class Exception implements Throwable, Stringable ] {
string(2194) "Class [ <internal:Core> class Exception implements Stringable, Throwable ] {
- Constants [0] {
}