Skip to content

Commit 90785dd

Browse files
authored
[RFC] Improve callbacks in ext/dom and ext/xsl (#12627)
1 parent 2b30f18 commit 90785dd

38 files changed

+1668
-521
lines changed

NEWS

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ DOM:
2222
. Implement DOM HTML5 parsing and serialization RFC. (nielsdos)
2323
. Fix DOMElement->prefix with empty string creates bogus prefix. (nielsdos)
2424
. Handle OOM more consistently. (nielsdos)
25+
. Implemented "Improve callbacks in ext/dom and ext/xsl" RFC. (nielsdos)
2526

2627
FPM:
2728
. Implement GH-12385 (flush headers without body when calling flush()).
@@ -147,5 +148,6 @@ XML:
147148
XSL:
148149
. Implement request #64137 (XSLTProcessor::setParameter() should allow both
149150
quotes to be used). (nielsdos)
151+
. Implemented "Improve callbacks in ext/dom and ext/xsl" RFC. (nielsdos)
150152

151153
<<< NOTE: Insert NEWS from last stable release here prior to actual release! >>>

UPGRADING

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,9 @@ PHP 8.4 UPGRADE NOTES
121121
. XSLTProcessor::setParameter() will now throw a ValueError when its arguments
122122
contain null bytes. This never actually worked correctly in the first place,
123123
which is why it throws an exception nowadays.
124+
. Failure to call a PHP function callback during evaluation now throws
125+
instead of emitting a warning.
126+
RFC: https://wiki.php.net/rfc/improve_callbacks_dom_and_xsl
124127

125128
========================================
126129
2. New Features
@@ -145,6 +148,8 @@ PHP 8.4 UPGRADE NOTES
145148
These classes provide a cleaner API to handle HTML and XML documents.
146149
Furthermore, the DOM\HTMLDocument class implements spec-compliant HTML5
147150
parsing and serialization.
151+
. It is now possible to pass any callable to registerPhpFunctions().
152+
RFC: https://wiki.php.net/rfc/improve_callbacks_dom_and_xsl
148153

149154
- FPM:
150155
. Flushing headers without a body will now succeed. See GH-12785.
@@ -195,6 +200,8 @@ PDO_SQLITE:
195200
- XSL:
196201
. It is now possible to use parameters that contain both single and double
197202
quotes.
203+
. It is now possible to pass any callable to registerPhpFunctions().
204+
RFC: https://wiki.php.net/rfc/improve_callbacks_dom_and_xsl
198205

199206
========================================
200207
3. Changes in SAPI modules
@@ -318,6 +325,8 @@ PDO_SQLITE:
318325

319326
- DOM:
320327
. Added DOMNode::compareDocumentPosition().
328+
. Added DOMXPath::registerPhpFunctionNS().
329+
RFC: https://wiki.php.net/rfc/improve_callbacks_dom_and_xsl
321330

322331
- MBString:
323332
. Added mb_trim, mb_ltrim and mb_rtrim functions.
@@ -334,6 +343,10 @@ PDO_SQLITE:
334343
. sodium_crypto_aead_aes256gcm_*() functions are now enabled on aarch64 CPUs
335344
with the ARM cryptographic extensions.
336345

346+
- XSL:
347+
. Added XSLTProcessor::registerPhpFunctionNS().
348+
RFC: https://wiki.php.net/rfc/improve_callbacks_dom_and_xsl
349+
337350
========================================
338351
7. New Classes and Interfaces
339352
========================================

UPGRADING.INTERNALS

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,8 @@ PHP 8.4 INTERNALS UPGRADE NOTES
5050
- dom_read_t and dom_write_t now expect the function to return zend_result
5151
instead of int.
5252
- The macros DOM_NO_ARGS() and DOM_NOT_IMPLEMENTED() have been removed.
53+
- New public APIs are available to handle callbacks from XPath, see
54+
xpath_callbacks.h.
5355

5456
b. ext/random
5557
- The macro RAND_RANGE_BADSCALING() has been removed. The implementation

ext/dom/config.m4

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ if test "$PHP_DOM" != "no"; then
3535
nodelist.c text.c comment.c \
3636
entityreference.c \
3737
notation.c xpath.c dom_iterators.c \
38-
namednodemap.c \
38+
namednodemap.c xpath_callbacks.c \
3939
$LEXBOR_SOURCES],
4040
$ext_shared,,$PHP_LEXBOR_CFLAGS)
4141
PHP_ADD_BUILD_DIR($ext_builddir/$LEXBOR_DIR/ports/posix/lexbor/core)
@@ -49,7 +49,7 @@ if test "$PHP_DOM" != "no"; then
4949
PHP_ADD_BUILD_DIR($ext_builddir/$LEXBOR_DIR/ns)
5050
PHP_ADD_BUILD_DIR($ext_builddir/$LEXBOR_DIR/tag)
5151
PHP_SUBST(DOM_SHARED_LIBADD)
52-
PHP_INSTALL_HEADERS([ext/dom/xml_common.h])
52+
PHP_INSTALL_HEADERS([ext/dom/xml_common.h ext/dom/xpath_callbacks.h])
5353
PHP_ADD_EXTENSION_DEP(dom, libxml)
5454
])
5555
fi

ext/dom/config.w32

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ if (PHP_DOM == "yes") {
1515
entity.c nodelist.c text.c comment.c \
1616
entityreference.c \
1717
notation.c xpath.c dom_iterators.c \
18-
namednodemap.c", null, "-Iext/dom/lexbor");
18+
namednodemap.c xpath_callbacks.c", null, "-Iext/dom/lexbor");
1919

2020
ADD_SOURCES("ext/dom/lexbor/lexbor/ports/windows_nt/lexbor/core", "memory.c", "dom");
2121
ADD_SOURCES("ext/dom/lexbor/lexbor/core", "array_obj.c array.c avl.c bst.c diyfp.c conv.c dobject.c dtoa.c hash.c mem.c mraw.c print.c serialize.c shs.c str.c strtod.c", "dom");
@@ -41,7 +41,7 @@ if (PHP_DOM == "yes") {
4141
WARNING("dom support can't be enabled, libxml is not found")
4242
}
4343
}
44-
PHP_INSTALL_HEADERS("ext/dom", "xml_common.h");
44+
PHP_INSTALL_HEADERS("ext/dom", "xml_common.h xpath_callbacks.h");
4545
} else {
4646
WARNING("dom support can't be enabled, libxml is not enabled")
4747
PHP_DOM = "no"

ext/dom/php_dom.c

Lines changed: 6 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -595,8 +595,10 @@ static int dom_nodelist_has_dimension(zend_object *object, zval *member, int che
595595
static zval *dom_nodemap_read_dimension(zend_object *object, zval *offset, int type, zval *rv);
596596
static int dom_nodemap_has_dimension(zend_object *object, zval *member, int check_empty);
597597
static zend_object *dom_objects_store_clone_obj(zend_object *zobject);
598+
598599
#ifdef LIBXML_XPATH_ENABLED
599600
void dom_xpath_objects_free_storage(zend_object *object);
601+
HashTable *dom_xpath_get_gc(zend_object *object, zval **table, int *n);
600602
#endif
601603

602604
static void *dom_malloc(size_t size) {
@@ -888,6 +890,7 @@ PHP_MINIT_FUNCTION(dom)
888890
memcpy(&dom_xpath_object_handlers, &dom_object_handlers, sizeof(zend_object_handlers));
889891
dom_xpath_object_handlers.offset = XtOffsetOf(dom_xpath_object, dom) + XtOffsetOf(dom_object, std);
890892
dom_xpath_object_handlers.free_obj = dom_xpath_objects_free_storage;
893+
dom_xpath_object_handlers.get_gc = dom_xpath_get_gc;
891894

892895
dom_xpath_class_entry = register_class_DOMXPath();
893896
dom_xpath_class_entry->create_object = dom_xpath_objects_new;
@@ -1000,32 +1003,6 @@ void node_list_unlink(xmlNodePtr node)
10001003
}
10011004
/* }}} end node_list_unlink */
10021005

1003-
#ifdef LIBXML_XPATH_ENABLED
1004-
/* {{{ dom_xpath_objects_free_storage */
1005-
void dom_xpath_objects_free_storage(zend_object *object)
1006-
{
1007-
dom_xpath_object *intern = php_xpath_obj_from_obj(object);
1008-
1009-
zend_object_std_dtor(&intern->dom.std);
1010-
1011-
if (intern->dom.ptr != NULL) {
1012-
xmlXPathFreeContext((xmlXPathContextPtr) intern->dom.ptr);
1013-
php_libxml_decrement_doc_ref((php_libxml_node_object *) &intern->dom);
1014-
}
1015-
1016-
if (intern->registered_phpfunctions) {
1017-
zend_hash_destroy(intern->registered_phpfunctions);
1018-
FREE_HASHTABLE(intern->registered_phpfunctions);
1019-
}
1020-
1021-
if (intern->node_list) {
1022-
zend_hash_destroy(intern->node_list);
1023-
FREE_HASHTABLE(intern->node_list);
1024-
}
1025-
}
1026-
/* }}} */
1027-
#endif
1028-
10291006
/* {{{ dom_objects_free_storage */
10301007
void dom_objects_free_storage(zend_object *object)
10311008
{
@@ -1132,12 +1109,13 @@ static void dom_object_namespace_node_free_storage(zend_object *object)
11321109
}
11331110

11341111
#ifdef LIBXML_XPATH_ENABLED
1112+
11351113
/* {{{ zend_object dom_xpath_objects_new(zend_class_entry *class_type) */
11361114
zend_object *dom_xpath_objects_new(zend_class_entry *class_type)
11371115
{
11381116
dom_xpath_object *intern = zend_object_alloc(sizeof(dom_xpath_object), class_type);
11391117

1140-
intern->registered_phpfunctions = zend_new_array(0);
1118+
php_dom_xpath_callbacks_ctor(&intern->xpath_callbacks);
11411119
intern->register_node_ns = 1;
11421120

11431121
intern->dom.prop_handler = &dom_xpath_prop_handlers;
@@ -1148,6 +1126,7 @@ zend_object *dom_xpath_objects_new(zend_class_entry *class_type)
11481126
return &intern->dom.std;
11491127
}
11501128
/* }}} */
1129+
11511130
#endif
11521131

11531132
void dom_nnodemap_objects_free_storage(zend_object *object) /* {{{ */

ext/dom/php_dom.h

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ extern zend_module_entry dom_module_entry;
5353

5454
#include "xml_common.h"
5555
#include "ext/libxml/php_libxml.h"
56+
#include "xpath_callbacks.h"
5657
#include "zend_exceptions.h"
5758
#include "dom_ce.h"
5859
/* DOM API_VERSION, please bump it up, if you change anything in the API
@@ -64,10 +65,8 @@ extern zend_module_entry dom_module_entry;
6465
#define DOM_NODESET XML_XINCLUDE_START
6566

6667
typedef struct _dom_xpath_object {
67-
int registerPhpFunctions;
68+
php_dom_xpath_callbacks xpath_callbacks;
6869
int register_node_ns;
69-
HashTable *registered_phpfunctions;
70-
HashTable *node_list;
7170
dom_object dom;
7271
} dom_xpath_object;
7372

ext/dom/php_dom.stub.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -932,6 +932,8 @@ public function registerNamespace(string $prefix, string $namespace): bool {}
932932

933933
/** @tentative-return-type */
934934
public function registerPhpFunctions(string|array|null $restrict = null): void {}
935+
936+
public function registerPhpFunctionNS(string $namespaceURI, string $name, callable $callable): void {}
935937
}
936938
#endif
937939

ext/dom/php_dom_arginfo.h

Lines changed: 13 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

ext/dom/tests/DOMXPath_callables.phpt

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
--TEST--
2+
registerPHPFunctions() with callables - legit cases
3+
--EXTENSIONS--
4+
dom
5+
--FILE--
6+
<?php
7+
8+
class MyClass {
9+
public static function dump(string $var) {
10+
var_dump($var);
11+
}
12+
}
13+
14+
class MyDOMXPath extends DOMXPath {
15+
public function registerCycle() {
16+
$this->registerPhpFunctions(["cycle" => array($this, "dummy")]);
17+
}
18+
19+
public function dummy(string $var) {
20+
echo "dummy: $var\n";
21+
}
22+
}
23+
24+
$doc = new DOMDocument();
25+
$doc->loadHTML('<a href="https://php.net">hello</a>');
26+
27+
echo "--- Legit cases: none ---\n";
28+
29+
$xpath = new DOMXPath($doc);
30+
$xpath->registerNamespace("php", "http://php.net/xpath");
31+
try {
32+
$xpath->evaluate("//a[php:function('var_dump', string(@href))]");
33+
} catch (Error $e) {
34+
echo $e->getMessage(), "\n";
35+
}
36+
37+
echo "--- Legit cases: all ---\n";
38+
39+
$xpath->registerPHPFunctions(null);
40+
$xpath->evaluate("//a[php:function('var_dump', string(@href))]");
41+
$xpath->evaluate("//a[php:function('MyClass::dump', string(@href))]");
42+
43+
echo "--- Legit cases: set ---\n";
44+
45+
$xpath = new DOMXPath($doc);
46+
$xpath->registerNamespace("php", "http://php.net/xpath");
47+
$xpath->registerPhpFunctions([]);
48+
$xpath->registerPHPFunctions(["xyz" => MyClass::dump(...), "mydump" => function (string $x) {
49+
var_dump($x);
50+
}]);
51+
$xpath->registerPhpFunctions(str_repeat("var_dump", mt_rand(1, 1) /* defeat SCCP */));
52+
$xpath->evaluate("//a[php:function('mydump', string(@href))]");
53+
$xpath->evaluate("//a[php:function('xyz', string(@href))]");
54+
$xpath->evaluate("//a[php:function('var_dump', string(@href))]");
55+
try {
56+
$xpath->evaluate("//a[php:function('notinset', string(@href))]");
57+
} catch (Throwable $e) {
58+
echo $e->getMessage(), "\n";
59+
}
60+
61+
echo "--- Legit cases: set with cycle ---\n";
62+
63+
$xpath = new MyDOMXPath($doc);
64+
$xpath->registerNamespace("php", "http://php.net/xpath");
65+
$xpath->registerCycle();
66+
$xpath->evaluate("//a[php:function('cycle', string(@href))]");
67+
68+
echo "--- Legit cases: reset to null ---\n";
69+
70+
$xpath->registerPhpFunctions(null);
71+
$xpath->evaluate("//a[php:function('var_dump', string(@href))]");
72+
73+
?>
74+
--EXPECT--
75+
--- Legit cases: none ---
76+
No callbacks were registered
77+
--- Legit cases: all ---
78+
string(15) "https://php.net"
79+
string(15) "https://php.net"
80+
--- Legit cases: set ---
81+
string(15) "https://php.net"
82+
string(15) "https://php.net"
83+
string(15) "https://php.net"
84+
No callback handler "notinset" registered
85+
--- Legit cases: set with cycle ---
86+
dummy: https://php.net
87+
--- Legit cases: reset to null ---
88+
string(15) "https://php.net"

0 commit comments

Comments
 (0)