Skip to content

Commit 94ffcf3

Browse files
committed
Auto-implement Stringable for string backend enums
1 parent 49d3dde commit 94ffcf3

File tree

3 files changed

+74
-1
lines changed

3 files changed

+74
-1
lines changed
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
--TEST--
2+
Backed string enum auto-implement Stringable
3+
--FILE--
4+
<?php
5+
6+
enum Foo: string {
7+
case Bar = 'Baz';
8+
}
9+
10+
function print_(string $value) {
11+
echo $value, "\n";
12+
}
13+
14+
echo (string) Foo::Bar, "\n";
15+
echo Foo::Bar, "\n";
16+
echo Foo::Bar . "\n";
17+
print_(Foo::Bar);
18+
var_dump(Foo::Bar instanceof Stringable);
19+
20+
?>
21+
--EXPECT--
22+
Baz
23+
Baz
24+
Baz
25+
Baz
26+
bool(true)

Zend/zend_enum.c

Lines changed: 46 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
#include "zend_API.h"
2121
#include "zend_compile.h"
2222
#include "zend_enum_arginfo.h"
23+
#include "zend_interfaces_arginfo.h"
2324
#include "zend_interfaces.h"
2425
#include "zend_enum.h"
2526

@@ -81,11 +82,14 @@ static void zend_verify_enum_magic_methods(zend_class_entry *ce)
8182
ZEND_ENUM_DISALLOW_MAGIC_METHOD(__set, "__set");
8283
ZEND_ENUM_DISALLOW_MAGIC_METHOD(__unset, "__unset");
8384
ZEND_ENUM_DISALLOW_MAGIC_METHOD(__isset, "__isset");
84-
ZEND_ENUM_DISALLOW_MAGIC_METHOD(__tostring, "__toString");
8585
ZEND_ENUM_DISALLOW_MAGIC_METHOD(__debugInfo, "__debugInfo");
8686
ZEND_ENUM_DISALLOW_MAGIC_METHOD(__serialize, "__serialize");
8787
ZEND_ENUM_DISALLOW_MAGIC_METHOD(__unserialize, "__unserialize");
8888

89+
if (ce->enum_backing_type != IS_STRING) {
90+
ZEND_ENUM_DISALLOW_MAGIC_METHOD(__tostring, "__toString");
91+
}
92+
8993
const char *forbidden_methods[] = {
9094
"__sleep",
9195
"__wakeup",
@@ -169,6 +173,10 @@ void zend_enum_add_interfaces(zend_class_entry *ce)
169173
ce->num_interfaces++;
170174
if (ce->enum_backing_type != IS_UNDEF) {
171175
ce->num_interfaces++;
176+
177+
if (ce->enum_backing_type == IS_STRING) {
178+
ce->num_interfaces++;
179+
}
172180
}
173181

174182
ZEND_ASSERT(!(ce->ce_flags & ZEND_ACC_RESOLVED_INTERFACES));
@@ -181,6 +189,11 @@ void zend_enum_add_interfaces(zend_class_entry *ce)
181189
if (ce->enum_backing_type != IS_UNDEF) {
182190
ce->interface_names[num_interfaces_before + 1].name = zend_string_copy(zend_ce_backed_enum->name);
183191
ce->interface_names[num_interfaces_before + 1].lc_name = zend_string_init("backedenum", sizeof("backedenum") - 1, 0);
192+
193+
if (ce->enum_backing_type == IS_STRING) {
194+
ce->interface_names[num_interfaces_before + 2].name = zend_string_copy(zend_ce_stringable->name);
195+
ce->interface_names[num_interfaces_before + 2].lc_name = zend_string_init("stringable", sizeof("stringable") - 1, 0);
196+
}
184197
}
185198
}
186199

@@ -394,6 +407,16 @@ static ZEND_NAMED_FUNCTION(zend_enum_try_from_func)
394407
zend_enum_from_base(INTERNAL_FUNCTION_PARAM_PASSTHRU, 1);
395408
}
396409

410+
static ZEND_NAMED_FUNCTION(zend_enum_tostring_func)
411+
{
412+
ZEND_PARSE_PARAMETERS_NONE();
413+
414+
zval *value = zend_enum_fetch_case_value(Z_OBJ_P(ZEND_THIS));
415+
ZEND_ASSERT(Z_TYPE_P(value) == IS_STRING);
416+
417+
RETURN_COPY(value);
418+
}
419+
397420
void zend_enum_register_funcs(zend_class_entry *ce)
398421
{
399422
const uint32_t fn_flags =
@@ -447,6 +470,28 @@ void zend_enum_register_funcs(zend_class_entry *ce)
447470
zend_error_noreturn(E_COMPILE_ERROR,
448471
"Cannot redeclare %s::tryFrom()", ZSTR_VAL(ce->name));
449472
}
473+
474+
if (ce->enum_backing_type == IS_STRING) {
475+
zend_internal_function *tostring_function =
476+
zend_arena_alloc(&CG(arena), sizeof(zend_internal_function));
477+
memset(tostring_function, 0, sizeof(zend_internal_function));
478+
tostring_function->type = ZEND_INTERNAL_FUNCTION;
479+
tostring_function->module = EG(current_module);
480+
tostring_function->handler = zend_enum_tostring_func;
481+
tostring_function->function_name = ZSTR_KNOWN(ZEND_STR_TOSTRING);
482+
tostring_function->scope = ce;
483+
tostring_function->fn_flags = ZEND_ACC_PUBLIC|ZEND_ACC_HAS_RETURN_TYPE|ZEND_ACC_ARENA_ALLOCATED;
484+
tostring_function->num_args = 0;
485+
tostring_function->required_num_args = 0;
486+
tostring_function->arg_info = (zend_internal_arg_info *) (arginfo_class_Stringable___toString + 1);
487+
if (!zend_hash_add_ptr(
488+
&ce->function_table, ZSTR_KNOWN(ZEND_STR_TOSTRING_LOWERCASE), tostring_function)) {
489+
zend_error_noreturn(E_COMPILE_ERROR,
490+
"Cannot redeclare %s::__toString()", ZSTR_VAL(ce->name));
491+
}
492+
493+
ce->__tostring = tostring_function;
494+
}
450495
}
451496
}
452497

Zend/zend_string.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -592,6 +592,8 @@ EMPTY_SWITCH_DEFAULT_CASE()
592592
_(ZEND_STR_AUTOGLOBAL_ENV, "_ENV") \
593593
_(ZEND_STR_AUTOGLOBAL_REQUEST, "_REQUEST") \
594594
_(ZEND_STR_COUNT, "count") \
595+
_(ZEND_STR_TOSTRING, "__toString") \
596+
_(ZEND_STR_TOSTRING_LOWERCASE, "__tostring") \
595597

596598

597599
typedef enum _zend_known_string_id {

0 commit comments

Comments
 (0)