Skip to content

Commit ce5ab06

Browse files
committed
Update to the new registerPhpFunctionNS proposal
1 parent 70817b9 commit ce5ab06

11 files changed

+280
-159
lines changed

ext/dom/php_dom.stub.php

+1-1
Original file line numberDiff line numberDiff line change
@@ -933,7 +933,7 @@ public function registerNamespace(string $prefix, string $namespace): bool {}
933933
/** @tentative-return-type */
934934
public function registerPhpFunctions(string|array|null $restrict = null): void {}
935935

936-
public function registerPhpFunctionsNS(string $namespace, string|array $restrict): void {}
936+
public function registerPhpFunctionNS(string $namespaceURI, string $name, callable $callable): void {}
937937
}
938938
#endif
939939

ext/dom/php_dom_arginfo.h

+7-6
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
+137
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
--TEST--
2+
registerPhpFunctionNS() function
3+
--EXTENSIONS--
4+
dom
5+
--FILE--
6+
<?php
7+
8+
class TrampolineClass {
9+
public static function __callStatic(string $name, array $arguments): mixed {
10+
var_dump($name, $arguments);
11+
return strtoupper($arguments[0]);
12+
}
13+
}
14+
15+
class StatefulClass {
16+
public array $state = [];
17+
18+
public function __call(string $name, array $arguments): mixed {
19+
$this->state[] = [$name, $arguments[0]];
20+
return $arguments[0];
21+
}
22+
}
23+
24+
$doc = new DOMDocument();
25+
$doc->loadHTML('<a href="https://PHP.net">hello</a>');
26+
27+
$xpath = new DOMXPath($doc);
28+
29+
echo "--- Error cases ---\n";
30+
31+
try {
32+
$xpath->registerPhpFunctionNS('http://php.net/xpath', 'strtolower', strtolower(...));
33+
} catch (ValueError $e) {
34+
echo $e->getMessage(), "\n";
35+
}
36+
37+
try {
38+
$xpath->registerPhpFunctionNS('urn:foo', 'x:a', strtolower(...));
39+
} catch (ValueError $e) {
40+
echo $e->getMessage(), "\n";
41+
}
42+
43+
try {
44+
$xpath->registerPhpFunctionNS("urn:foo", "\0", strtolower(...));
45+
} catch (ValueError $e) {
46+
echo $e->getMessage(), "\n";
47+
}
48+
49+
try {
50+
$xpath->registerPhpFunctionNS("\0", 'strtolower', strtolower(...));
51+
} catch (ValueError $e) {
52+
echo $e->getMessage(), "\n";
53+
}
54+
55+
$xpath->registerNamespace('foo', 'urn:foo');
56+
57+
echo "--- Legit cases: global function callable ---\n";
58+
59+
$xpath->registerPhpFunctionNS('urn:foo', 'strtolower', strtolower(...));
60+
var_dump($xpath->query('//a[foo:strtolower(string(@href)) = "https://php.net"]'));
61+
62+
echo "--- Legit cases: string callable ---\n";
63+
64+
$xpath->registerPhpFunctionNS('urn:foo', 'strtolower', 'strtolower');
65+
var_dump($xpath->query('//a[foo:strtolower(string(@href)) = "https://php.net"]'));
66+
67+
echo "--- Legit cases: trampoline callable ---\n";
68+
69+
$xpath->registerPhpFunctionNS('urn:foo', 'test', TrampolineClass::test(...));
70+
var_dump($xpath->query('//a[foo:test(string(@href)) = "https://php.net"]'));
71+
72+
echo "--- Legit cases: instance class method callable ---\n";
73+
74+
$state = new StatefulClass;
75+
$xpath->registerPhpFunctionNS('urn:foo', 'test', $state->test(...));
76+
var_dump($xpath->query('//a[foo:test(string(@href))]'));
77+
var_dump($state->state);
78+
79+
echo "--- Legit cases: global function callable that returns nothing ---\n";
80+
81+
$xpath->registerPhpFunctionNS('urn:foo', 'test', var_dump(...));
82+
$xpath->query('//a[foo:test(string(@href))]');
83+
84+
echo "--- Legit cases: multiple namespaces ---\n";
85+
86+
$xpath->registerNamespace('bar', 'urn:bar');
87+
$xpath->registerPhpFunctionNS('urn:bar', 'test', 'strtolower');
88+
var_dump($xpath->query('//a[bar:test(string(@href)) = "https://php.net"]'));
89+
90+
?>
91+
--EXPECT--
92+
--- Error cases ---
93+
DOMXPath::registerPhpFunctionNS(): Argument #1 ($namespaceURI) must not be "http://php.net/xpath" because it is reserved for PHP
94+
DOMXPath::registerPhpFunctionNS(): Argument #2 ($name) must be a valid callback name
95+
DOMXPath::registerPhpFunctionNS(): Argument #2 ($name) must not contain any null bytes
96+
DOMXPath::registerPhpFunctionNS(): Argument #1 ($namespaceURI) must not contain any null bytes
97+
--- Legit cases: global function callable ---
98+
object(DOMNodeList)#7 (1) {
99+
["length"]=>
100+
int(1)
101+
}
102+
--- Legit cases: string callable ---
103+
object(DOMNodeList)#7 (1) {
104+
["length"]=>
105+
int(1)
106+
}
107+
--- Legit cases: trampoline callable ---
108+
string(4) "test"
109+
array(1) {
110+
[0]=>
111+
string(15) "https://PHP.net"
112+
}
113+
object(DOMNodeList)#3 (1) {
114+
["length"]=>
115+
int(0)
116+
}
117+
--- Legit cases: instance class method callable ---
118+
object(DOMNodeList)#8 (1) {
119+
["length"]=>
120+
int(1)
121+
}
122+
array(1) {
123+
[0]=>
124+
array(2) {
125+
[0]=>
126+
string(4) "test"
127+
[1]=>
128+
string(15) "https://PHP.net"
129+
}
130+
}
131+
--- Legit cases: global function callable that returns nothing ---
132+
string(15) "https://PHP.net"
133+
--- Legit cases: multiple namespaces ---
134+
object(DOMNodeList)#7 (1) {
135+
["length"]=>
136+
int(1)
137+
}

ext/dom/tests/registerPhpFunctionsNS.phpt

-91
This file was deleted.

ext/dom/xpath.c

+10-9
Original file line numberDiff line numberDiff line change
@@ -415,30 +415,31 @@ static void dom_xpath_register_func_in_ctx(void *ctxt, const zend_string *ns, co
415415
xmlXPathRegisterFuncNS((xmlXPathContextPtr) ctxt, (const xmlChar *) ZSTR_VAL(name), (const xmlChar *) ZSTR_VAL(ns), dom_xpath_ext_function_trampoline);
416416
}
417417

418-
PHP_METHOD(DOMXPath, registerPhpFunctionsNS)
418+
PHP_METHOD(DOMXPath, registerPhpFunctionNS)
419419
{
420420
dom_xpath_object *intern = Z_XPATHOBJ_P(ZEND_THIS);
421421

422-
zend_string *namespace;
423-
zend_string *callable_name;
424-
HashTable *callable_ht;
422+
zend_string *namespace, *name;
423+
zend_fcall_info fci;
424+
zend_fcall_info_cache fcc;
425425

426-
ZEND_PARSE_PARAMETERS_START(2, 2)
426+
ZEND_PARSE_PARAMETERS_START(3, 3)
427427
Z_PARAM_PATH_STR(namespace)
428-
Z_PARAM_ARRAY_HT_OR_STR(callable_ht, callable_name)
428+
Z_PARAM_PATH_STR(name)
429+
Z_PARAM_FUNC_NO_TRAMPOLINE_FREE(fci, fcc)
429430
ZEND_PARSE_PARAMETERS_END();
430431

431432
if (zend_string_equals_literal(namespace, "http://php.net/xpath")) {
432433
zend_argument_value_error(1, "must not be \"http://php.net/xpath\" because it is reserved for PHP");
433434
RETURN_THROWS();
434435
}
435436

436-
php_dom_xpath_callbacks_update_method_handler(
437+
php_dom_xpath_callbacks_update_single_method_handler(
437438
&intern->xpath_callbacks,
438439
intern->dom.ptr,
439440
namespace,
440-
callable_name,
441-
callable_ht,
441+
name,
442+
&fcc,
442443
PHP_DOM_XPATH_CALLBACK_NAME_VALIDATE_NCNAME,
443444
dom_xpath_register_func_in_ctx
444445
);

0 commit comments

Comments
 (0)