ext/zend_test: Test zend_call_method_if_exists()

This commit is contained in:
Gina Peter Banyard 2025-07-29 10:46:15 +01:00
parent a9f3e3c44e
commit fc6c49cbf4
17 changed files with 391 additions and 1 deletions

View file

@ -514,6 +514,28 @@ static ZEND_FUNCTION(zend_object_init_with_constructor)
ZVAL_COPY_VALUE(return_value, &obj); ZVAL_COPY_VALUE(return_value, &obj);
} }
static ZEND_FUNCTION(zend_call_method_if_exists)
{
zend_object *obj = NULL;
zend_string *method_name;
uint32_t num_args = 0;
zval *args = NULL;
ZEND_PARSE_PARAMETERS_START(2, -1)
Z_PARAM_OBJ(obj)
Z_PARAM_STR(method_name)
Z_PARAM_VARIADIC('*', args, num_args)
ZEND_PARSE_PARAMETERS_END();
zend_result status = zend_call_method_if_exists(obj, method_name, return_value, num_args, args);
if (status == FAILURE) {
ZEND_ASSERT(Z_ISUNDEF_P(return_value));
if (EG(exception)) {
RETURN_THROWS();
}
RETURN_NULL();
}
}
static ZEND_FUNCTION(zend_get_unit_enum) static ZEND_FUNCTION(zend_get_unit_enum)
{ {
ZEND_PARSE_PARAMETERS_NONE(); ZEND_PARSE_PARAMETERS_NONE();

View file

@ -293,6 +293,8 @@ namespace {
function zend_object_init_with_constructor(string $class, mixed ...$args): mixed {} function zend_object_init_with_constructor(string $class, mixed ...$args): mixed {}
function zend_call_method_if_exists(object $obj, string $method, mixed ...$args): mixed {}
function zend_test_zend_ini_parse_quantity(string $str): int {} function zend_test_zend_ini_parse_quantity(string $str): int {}
function zend_test_zend_ini_parse_uquantity(string $str): int {} function zend_test_zend_ini_parse_uquantity(string $str): int {}

View file

@ -1,5 +1,5 @@
/* This is a generated file, edit the .stub.php file instead. /* This is a generated file, edit the .stub.php file instead.
* Stub hash: 13a5559e87cb073c921006bb3be5354b90247306 */ * Stub hash: 073039fa0d9c41eb842f6944eb4acfc9217103cf */
ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_zend_test_array_return, 0, 0, IS_ARRAY, 0) ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_zend_test_array_return, 0, 0, IS_ARRAY, 0)
ZEND_END_ARG_INFO() ZEND_END_ARG_INFO()
@ -113,6 +113,12 @@ ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_zend_object_init_with_constructo
ZEND_ARG_VARIADIC_TYPE_INFO(0, args, IS_MIXED, 0) ZEND_ARG_VARIADIC_TYPE_INFO(0, args, IS_MIXED, 0)
ZEND_END_ARG_INFO() ZEND_END_ARG_INFO()
ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_zend_call_method_if_exists, 0, 2, IS_MIXED, 0)
ZEND_ARG_TYPE_INFO(0, obj, IS_OBJECT, 0)
ZEND_ARG_TYPE_INFO(0, method, IS_STRING, 0)
ZEND_ARG_VARIADIC_TYPE_INFO(0, args, IS_MIXED, 0)
ZEND_END_ARG_INFO()
ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_zend_test_zend_ini_parse_quantity, 0, 1, IS_LONG, 0) ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_zend_test_zend_ini_parse_quantity, 0, 1, IS_LONG, 0)
ZEND_ARG_TYPE_INFO(0, str, IS_STRING, 0) ZEND_ARG_TYPE_INFO(0, str, IS_STRING, 0)
ZEND_END_ARG_INFO() ZEND_END_ARG_INFO()
@ -293,6 +299,7 @@ static ZEND_FUNCTION(zend_test_attribute_with_named_argument);
static ZEND_FUNCTION(zend_get_current_func_name); static ZEND_FUNCTION(zend_get_current_func_name);
static ZEND_FUNCTION(zend_call_method); static ZEND_FUNCTION(zend_call_method);
static ZEND_FUNCTION(zend_object_init_with_constructor); static ZEND_FUNCTION(zend_object_init_with_constructor);
static ZEND_FUNCTION(zend_call_method_if_exists);
static ZEND_FUNCTION(zend_test_zend_ini_parse_quantity); static ZEND_FUNCTION(zend_test_zend_ini_parse_quantity);
static ZEND_FUNCTION(zend_test_zend_ini_parse_uquantity); static ZEND_FUNCTION(zend_test_zend_ini_parse_uquantity);
static ZEND_FUNCTION(zend_test_zend_ini_str); static ZEND_FUNCTION(zend_test_zend_ini_str);
@ -419,6 +426,7 @@ static const zend_function_entry ext_functions[] = {
ZEND_FE(zend_get_current_func_name, arginfo_zend_get_current_func_name) ZEND_FE(zend_get_current_func_name, arginfo_zend_get_current_func_name)
ZEND_FE(zend_call_method, arginfo_zend_call_method) ZEND_FE(zend_call_method, arginfo_zend_call_method)
ZEND_FE(zend_object_init_with_constructor, arginfo_zend_object_init_with_constructor) ZEND_FE(zend_object_init_with_constructor, arginfo_zend_object_init_with_constructor)
ZEND_FE(zend_call_method_if_exists, arginfo_zend_call_method_if_exists)
ZEND_FE(zend_test_zend_ini_parse_quantity, arginfo_zend_test_zend_ini_parse_quantity) ZEND_FE(zend_test_zend_ini_parse_quantity, arginfo_zend_test_zend_ini_parse_quantity)
ZEND_FE(zend_test_zend_ini_parse_uquantity, arginfo_zend_test_zend_ini_parse_uquantity) ZEND_FE(zend_test_zend_ini_parse_uquantity, arginfo_zend_test_zend_ini_parse_uquantity)
ZEND_FE(zend_test_zend_ini_str, arginfo_zend_test_zend_ini_str) ZEND_FE(zend_test_zend_ini_str, arginfo_zend_test_zend_ini_str)

View file

@ -0,0 +1,27 @@
--TEST--
zend_call_method_if_exists() with existing non public methods
--EXTENSIONS--
zend_test
--FILE--
<?php
class A {
protected function foo() {
return __METHOD__;
}
private function bar() {
return __METHOD__;
}
}
$a = new A();
$r = zend_call_method_if_exists($a, 'foo');
var_dump($r);
$r = zend_call_method_if_exists($a, 'bar');
var_dump($r);
?>
--EXPECT--
NULL
NULL

View file

@ -0,0 +1,23 @@
--TEST--
zend_call_method_if_exists() with existing public method
--EXTENSIONS--
zend_test
--FILE--
<?php
class C {
private function priv() {
var_dump(__METHOD__);
}
public function test() {
// this should call $c->priv()
zend_call_method_if_exists($this, 'priv');
}
}
$c = new C();
$c->test();
?>
--EXPECT--
string(7) "C::priv"

View file

@ -0,0 +1,21 @@
--TEST--
zend_call_method_if_exists() with existing public method
--EXTENSIONS--
zend_test
--FILE--
<?php
class A {
public function foo() {
return __METHOD__;
}
}
$a = new A();
$r = zend_call_method_if_exists($a, 'foo');
var_dump($r);
?>
--EXPECT--
string(6) "A::foo"

View file

@ -0,0 +1,27 @@
--TEST--
zend_call_method_if_exists() with existing non public static methods
--EXTENSIONS--
zend_test
--FILE--
<?php
class A {
protected static function foo() {
return __METHOD__;
}
private static function bar() {
return __METHOD__;
}
}
$a = new A();
$r = zend_call_method_if_exists($a, 'foo');
var_dump($r);
$r = zend_call_method_if_exists($a, 'bar');
var_dump($r);
?>
--EXPECT--
NULL
NULL

View file

@ -0,0 +1,21 @@
--TEST--
zend_call_method_if_exists() with existing static public method
--EXTENSIONS--
zend_test
--FILE--
<?php
class A {
public static function foo() {
return __METHOD__;
}
}
$a = new A();
$r = zend_call_method_if_exists($a, 'foo');
var_dump($r);
?>
--EXPECT--
string(6) "A::foo"

View file

@ -0,0 +1,27 @@
--TEST--
zend_call_method_if_exists() with non existing method on extended class with a trampoline
--EXTENSIONS--
zend_test
--FILE--
<?php
class A {
public function __call(string $name, array $arguments): string {
return "In A trampoline for $name!";
}
}
class B extends A {
public function __call(string $name, array $arguments): string {
return "In B trampoline for $name!";
}
}
$b = new B();
$r = zend_call_method_if_exists($b, 'bar');
var_dump($r);
?>
--EXPECT--
string(24) "In B trampoline for bar!"

View file

@ -0,0 +1,23 @@
--TEST--
zend_call_method_if_exists() with non existing method on extended class with a trampoline
--EXTENSIONS--
zend_test
--FILE--
<?php
class A {
public function __call(string $name, array $arguments): string {
return "In A trampoline for $name!";
}
}
class B extends A {}
$b = new B();
$r = zend_call_method_if_exists($b, 'bar');
var_dump($r);
?>
--EXPECT--
string(24) "In A trampoline for bar!"

View file

@ -0,0 +1,25 @@
--TEST--
zend_call_method_if_exists() with throwing method
--EXTENSIONS--
zend_test
--FILE--
<?php
class A {
public function foo() {
throw new Exception("Error");
}
}
$a = new A();
try {
$r = zend_call_method_if_exists($a, 'foo');
var_dump($r);
} catch (Throwable $e) {
echo $e::class, ': ', $e->getMessage(), PHP_EOL;
}
?>
--EXPECT--
Exception: Error

View file

@ -0,0 +1,44 @@
--TEST--
zend_call_method_if_exists() with non existing method on class with a trampoline and static trampoline
--EXTENSIONS--
zend_test
--FILE--
<?php
class A {
public function foo() {
return __METHOD__;
}
public function __call(string $name, array $arguments): string {
return "In A trampoline for $name!";
}
public static function __callStatic(string $name, array $arguments): string {
return "In A static trampoline for $name!";
}
}
class B {
public function foo() {
return __METHOD__;
}
public static function __callStatic(string $name, array $arguments): string {
return "In B static trampoline for $name!";
}
public function __call(string $name, array $arguments): string {
return "In B trampoline for $name!";
}
}
$a = new A();
$r = zend_call_method_if_exists($a, 'bar');
var_dump($r);
$b = new B();
$r = zend_call_method_if_exists($b, 'bar');
var_dump($r);
?>
--EXPECT--
string(24) "In A trampoline for bar!"
string(24) "In B trampoline for bar!"

View file

@ -0,0 +1,24 @@
--TEST--
zend_call_method_if_exists() with non existing method on class with a static trampoline
--EXTENSIONS--
zend_test
--FILE--
<?php
class A {
public function foo() {
return __METHOD__;
}
public static function __callStatic(string $name, array $arguments): string {
return "In static trampoline for $name!";
}
}
$a = new A();
$r = zend_call_method_if_exists($a, 'bar');
var_dump($r);
?>
--EXPECT--
NULL

View file

@ -0,0 +1,24 @@
--TEST--
zend_call_method_if_exists() with non existing method on class with a trampoline
--EXTENSIONS--
zend_test
--FILE--
<?php
class A {
public function foo() {
return __METHOD__;
}
public function __call(string $name, array $arguments): string {
return "In trampoline for $name!";
}
}
$a = new A();
$r = zend_call_method_if_exists($a, 'bar');
var_dump($r);
?>
--EXPECT--
string(22) "In trampoline for bar!"

View file

@ -0,0 +1,21 @@
--TEST--
zend_call_method_if_exists() with non existing method
--EXTENSIONS--
zend_test
--FILE--
<?php
class A {
public function foo() {
return __METHOD__;
}
}
$a = new A();
$r = zend_call_method_if_exists($a, 'bar');
var_dump($r);
?>
--EXPECT--
NULL

View file

@ -0,0 +1,24 @@
--TEST--
zend_call_method_if_exists() shadowing a private method
--EXTENSIONS--
zend_test
--FILE--
<?php
class A {
private function foo() {
return 'Not available';
}
public function __call(string $name, array $arguments): string {
return "In trampoline for $name!";
}
}
$a = new A();
$r = zend_call_method_if_exists($a, 'foo');
var_dump($r);
?>
--EXPECT--
string(22) "In trampoline for foo!"

View file

@ -0,0 +1,27 @@
--TEST--
zend_call_method_if_exists() shadowing a private method
--EXTENSIONS--
zend_test
--FILE--
<?php
class A {
private function foo() {
return 'Not available';
}
}
class B extends A {
public function foo() {
return __METHOD__;
}
}
$b = new B();
$r = zend_call_method_if_exists($b, 'foo');
var_dump($r);
?>
--EXPECT--
string(6) "B::foo"